标签:封装 多个 面向对象编程 abs visit new lsa arraylist 数据
访问者模式定义如下:封装一些作用于某种数据结构中各个元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
下面看一下几个抽象角色:
看一下访问者模式的通用代码:
//抽象元素 public abstract class Element{ //定义业务逻辑 public abstract void doSomething(); //允许谁来访问 public abstract void accept(IVisitor visitor); } //具体元素 public class ConcreateElement1 extends Element{ //完善业务逻辑 public void doSomething(){ //业务逻辑 } //允许哪个访问者来访问 public void accept(IVisitor visitor){ visitor.visit(this); } } public class ConcreateElement2 extends Element{ //完善业务逻辑 public void doSomething(){ //业务逻辑 } //允许哪个访问者来访问 public void accept(IVisitor visitor){ visitor.visit(this); } }
再来看看抽象访问者,一般是有几个具体元素就有几个访问方法:
//抽象访问者 public interface IVisitor{ //可以访问哪些对象 public void visit(ConcreateElement1 el1); public void visit(ConcreateElement2 el2); } //具体访问者 public class Visitor implements IVIsitor{ //访问el1元素 public void visit(ConcreateElement1 el1){ el1.doSomething(); } //访问el2元素 public void visit(ConcreateElement2 el2){ el2.doSomething(); } }
结构对象是产生出不同的元素对象,这里使用工厂方法模式来模式:
//结构对象 public class ObjectStruture { //对象生成器, 这里通过一个工厂方法模式模拟 public static Element createElement(){ Random rand = new Random(); if(rand.nextInt(100) > 50){ return new ConcreteElement1(); }else{ return new ConcreteElement2(); } } }
进入访问者角色后,我们对所有的具体元素的访问就非常简单了,这里通过一个场景类来模拟:
public class Client{ public static void main(String args[]){ for(int i=0;i<10;i++){ //获得元素对象 Element el = ObjectStructure.createElement(); //接收访问者访问 el.accept(new Visitor()); } } }
通过增加访问者,只要是具体元素就非常容易访问,不管是什么对象,只要它在一个容器中,都可以通过访问者来访问,任务集中化,这就是访问者模式。
优点:
缺点:
使用场景:
访问者模式的扩展:
比如金融系统中常用的汇总和报表功能,一堆的计算公式,然后出一堆报表,很多项目使用存储过程来实现,但不是很推荐,除非是海量数据,批处理上亿、上百亿条的数据,除了存储过程没有其他办法,如果用应用服务器,数据库连接将一直是100%的连接状态,如果不是这种海量数据,数据报表和统计功能使用访问者模式会更简单。
下面看代码示例:
//抽象访问者 public interface IVisitor{ //首先定义我可以访问普通员工 public void visit(CommonEmployee commonEmployee); //其次定义,我可以访问部门经理 public void visit(Manager manager); //统计所有员工工资之和 public int getTotalSalary(); } //具体访问者 public class Visitor implements IVisitor{ //部门经理的工资系数是5 private final static int MANAGER_COEFFICIENT = 5; //员工的工资系数是2 private final static int COMMONEMPLOYEE_COEFFICIENT = 2; //普通员工的工资总和 private int commonTotalSalary = 0; //部门经理的工资总和 private int managerTotalSalary =0; //计算部门经理的工资总和 private void calManagerSalary(int salary){ this.managerTotalSalary = this.managerTotalSalary + salary *MANAGER_COEFFICIENT ; } //计算普通员工的工资总和 private void calCommonSlary(int salary){ this.commonTotalSalary = this.commonTotalSalary + salary*COMMONEMPLOYEE_COEFFICIENT; } //获得所有员工的工资总和 public int getTotalSalary(){ return this.commonTotalSalary + this.managerTotalSalary; } }
员工和经理类暂时不列出了,程序很简单,现在看一个场景类:
//场景类 public class Client{ public static void main(String[] args){ IVisitor visitor = new Visitor(); for(Employee emp:mockEmployee()){ emp.accept(visitor); } System.out.println("工资总和是:"+visitor.getTotalSalary()); } //模拟出公司的人员情况, 我们可以想象这个数据是通过持久层传递过来的 public static List<Employee> mockEmployee(){ List<Employee> empList = new ArrayList<Employee>(); //产生张三这个员工 CommonEmployee zhangSan = new CommonEmployee(); zhangSan.setJob("编写Java程序, 绝对的蓝领、 苦工加搬运工"); zhangSan.setName("张三"); zhangSan.setSalary(1800); zhangSan.setSex(Employee.MALE); empList.add(zhangSan); //产生李四这个员工 CommonEmployee liSi = new CommonEmployee(); liSi.setJob("页面美工, 审美素质太不流行了! "); liSi.setName("李四"); liSi.setSalary(1900); liSi.setSex(Employee.FEMALE); empList.add(liSi); //再产生一个经理 Manager wangWu = new Manager(); wangWu.setName("王五"); wangWu.setPerformance("基本上是负值, 但是我会拍马屁呀"); wangWu.setSalary(18750); wangWu.setSex(Employee.MALE); empList.add(wangWu); return empList; } }
在实际项目中,一个对象,可能有多个访问者。比如上面报表的例子,报表分两种:第一种是展示表,通过数据库查询,把结果展示出来,类似于列表;第二种是汇总表,需要通过模型或公式计算出来的,一般都是批处理结果,比如计算工资平均值或总值,两种方式是对同一堆数据的不同处理方式。从程序上看,一个类就有了两个访问者了。下面看代码
//展示表接口 public interface IShowVisitor extends IVisitor{ //展示报表 public void report(); } //展示具体表 public class ShowVisitor implements IShowVisitor{ private String info = ""; //打印报表 public void report(){ System.out.println(this.info); } //访问普通员工,组装信息 public void visit(CommonEmployee commonEmployee){ this.info = this.info + this.getBasicInfo(commonEmployee) + "工作: "+commonEmployee.getJob()+"\t\n"; } public void visit(Manager manager){ this.info = this.info + this.getBasicInfo(manager) + "业绩: "+manager.getPerformance() + "\t\n"; } //组装出基本信息 private String getBasicInfo(Employee employee){ String info = "姓名: " + employee.getName() + "\t"; info = info + "性别: " + (employee.getSex() ==Employee.FEMALE?"女":"男") + "\t"; info = info + "薪水: " + employee.getSalary() + "\t"; return info; } }
汇总表实现数据汇总功能:
//汇总表接口 public interface ITotalVisitor extends IVisitor { //统计所有员工工资总和 public void totalSalary(); } //具体汇总表 public class TotalVisitor implements ITotalVisitor{ //部门经理的工资系数是5 private final static int MANAGER_COEFFICIENT = 5; //员工的工资系数是2 private final static int COMMONEMPLOYEE_COEFFICIENT = 2; //普通员工的工资总和 private int commonTotalSalary = 0; //部门经理的工资总和 private int managerTotalSalary =0; public void totalSalary() { System.out.println("本公司的月工资总额是" + (this.commonTotalSalary +this.managerTotalSalary)); } //访问普通员工, 计算工资总额 public void visit(CommonEmployee commonEmployee) { this.commonTotalSalary = this.commonTotalSalary + commonEmployee.getSal } //访问部门经理, 计算工资总额 public void visit(Manager manager) { this.managerTotalSalary = this.managerTotalSalary + manager.getSalary() } }
最后在场景类计算工资总和:
public class Client { public static void main(String[] args) { //展示报表访问者 IShowVisitor showVisitor = new ShowVisitor(); //汇总报表的访问者 ITotalVisitor totalVisitor = new TotalVisitor(); for(Employee emp:mockEmployee()){ emp.accept(showVisitor); //接受展示报表访问者 emp.accept(totalVisitor);//接受汇总表访问者 } //展示报表 showVisitor.report(); //汇总报表 totalVisitor.totalSalary(); } }
什么是双分派?先解释下双分派和单分派。单分派语言处理一个操作是根据请求者的名称和接收到的参数决定的,在java中有动态绑定和静态绑定之说,它的实现是依据重载和覆写实现的。举个栗子, 演员演电影角色,一个演员可以演多个角色,我们先定义两个角色:功夫主角和白痴配角。代码如下:
//角色接口和实现类 public interface Role{ //演员要扮演的角色 } public class KungfuRole implements Role{ //武功第一角色 } public class IdiotRole implements Role{ //白痴配角 } //抽象演员 使用重载方式实现 public abstract class AbsActor{ //演员都能演一个角色 public void act(Role role){ } //演一个功夫角色 public void act(KungFuRole role){ } } //使用覆写方式实现 public class YoungActor extends AbsActor{ //年轻演员演功夫戏 public void act(KungFuRole role){ } } public class OldActor extends AbsActor{ //不演功夫角色 public void act(KungFuRole role){ System.out.println("年纪大了,不演功夫角色"); } } //场景类 public class Client{ public static void main(String[] args){ //定义一个演员 覆写方法 AbsActor actor = new OldActor(); //定义一个角色 重载方法 Role role = new KungFuRole(); //开始演戏 actor.act(role); actor.act(new KungFuRole()); } }
重载在编译器期就决定了要调用哪个方法, 它是根据role的表面类型而决定调用act(Rolerole)方法, 这是静态绑定; 而Actor的执行方法act则是由其实际类型决定的, 这是动态绑定。一个演员可以扮演很多角色, 我们的系统要适应这种变化, 也就是根据演员、 角色两个对象类型, 完成不同的操作任务, 该如何实现呢? 很简单, 我们让访问者模式上场就可以解决该问题, 只要把角色类稍稍修改即可。
public interface Role { //演员要扮演的角色 public void accept(AbsActor actor); } public class KungFuRole implements Role { //武功天下第一的角色 public void accept(AbsActor actor){ actor.act(this); } } public class IdiotRole implements Role { //一个弱智角色, 由谁来扮演 public void accept(AbsActor actor){ actor.act(this); } } public class Client { public static void main(String[] args) { //定义一个演员 AbsActor actor = new OldActor(); //定义一个角色 Role role = new KungFuRole(); //开始演戏 role.accept(actor); } }
不管演员类和角色类怎么变化, 我们都能够找到期望的方法运行, 这就是双反派。 双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型, 它是多分派的一
个特例。 从这里也可以看到Java是一个支持双分派的单分派语言。
标签:封装 多个 面向对象编程 abs visit new lsa arraylist 数据
原文地址:https://www.cnblogs.com/loveBolin/p/9752149.html