设计模式——访问者模式
访问者模式
访问者模式(Visitor Pattern):封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
访问者模式就是根据朋友的信息,执行了自己的一个方法。
访问者模式中的角色
- Visitor抽象访问者:抽象类或接口,声明访问者可以访问的元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。
- ConcreteVisitor具体访问者:访问者访问到一个类后该做的事情。
- Element抽象元素:接口或者抽象类,声明接受哪一类访问者访问,程序上是通过accept方法中的参数来定义的。
- ConcreteElement具体元素:实现accept方法,通常是visitor.visit(this),基本上都形成了一种模式了。
- ObjectStruture结构对象:元素产生者,一般容纳在多个不同类、不同接口的容器,如List、Set、Map等,在项目中,一般很少抽象出这个角色。
抽象元素Element:
public abstract class Element { //定义业务逻辑 public abstract void doSomething(); //允许谁来访问 public abstract void accept(IVisitor visitor);
}
具体元素ConcreteElement1~2:
public class ConcreteElement1 extends Element{ //完善业务逻辑 public void doSomething(){ //业务处理 }//允许那个访问者访问 public void accept(IVisitor visitor){ visitor.visit(this); }
}public class ConcreteElement2 extends Element{ //完善业务逻辑 public void doSomething(){ //业务处理 }//允许那个访问者访问 public void accept(IVisitor visitor){ visitor.visit(this); }
}
抽象访问者IVisitor:
public interface IVisitor { //可以访问哪些对象 public void visit(ConcreteElement1 el1); public void visit(ConcreteElement2 el2);
}
具体访问者Visitor:
public class Visitor implements IVisitor { //访问el1元素 public void visit(ConcreteElement1 el1) { el1.doSomething(); }//访问el2元素 public void visit(ConcreteElement2 el2) { el2.doSomething(); }
}
结构对象ObjectStruture:
public class ObjectStruture { //对象生成器,这里通过一个工厂方法模式模拟 public static Element createElement(){ Random rand = new Random(); if(rand.nextInt(100) > 50){ return new ConcreteElement1(); }else{ return new ConcreteElement2(); } }
}
场景类Client:
public class Client { public static void main(String[] args) { for(int i=0;i<10;i++){ //获得元素对象Element el = ObjectStruture.createElement(); //接受访问者访问 el.accept(new Visitor()); } }
}
访问者模式的优点:
- 符合单一职责原则:具体元素角色也就是Employee抽象类的两个子类负责数据的加载,而Visitor类则负责报表的展现,两个不同的职责非常明确地分离开来,各自演绎变化。
- 优秀的扩展性:由于职责分开,继续增加对数据的操作是非常快捷的。
- 灵活性非常高。
访问者模式的缺点:
- 具体元素对访问者公布细节,这是迪米特法则所不建议的。
- 具体元素变更比较困难:
- 具体元素角色的增加、删除、修改都是比较困难的。
- 违背了依赖倒置原则:访问者依赖的是具体元素,而不是抽象元素,抛弃了对接口的依赖,而直接依赖实现类,扩展比较难。
访问者模式的使用场景:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作,也就说是用迭代器模式已经不能胜任的情景。
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
访问者模式的扩展
- 统计功能
- 多个访问者
- 双分派
访问者模式的实例
- 大老板就看部门经理的报表,小兵的报表可看可不看。
- 多个大老板的“嗜好”是不同的,主管销售的,则主要关心营销的情况;主管会计的, 则主要关心企业的整体财务运行状态;主管技术的,则主要看技术的研发情况。
每一个员工的信息(如名字、性别、薪水等)都是记录在数据库中,根据这样的需求,把公司中的所有人员信息都打印汇报上去。
(1)访问者模式
★ 定义雇员类,其中包含所有雇员的公有属性,并定义允许访问者访问的方法accept()。
public abstract class Employee {public final static int MALE = 0; //男性public final static int FEMALE = 1; //女性private String name;private int salary;private int sex;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getSalary() {return salary;}public void setSalary(int salary) {this.salary = salary;}public int getSex() {return sex;}public void setSex(int sex) {this.sex = sex;}//允许一个访问者访问public abstract void accept(IVisitor visitor);
}
★ 定义普通员工类,其中包括普通员工的特有属性,并实现雇员类,允许IVisitor接口下的访问者。
import com.sfq.impl.Employee;
import com.sfq.impl.IVisitor;
public class CommonEmployee extends Employee {private String job;public String getJob() {return job;}public void setJob(String job) {this.job = job;}@Overridepublic void accept(IVisitor visitor) {visitor.visit(this);}
}
★ 定义经理类,其中包括经理的特有属性,并实现雇员类,允许IVisitor接口下的访问者访问。
import com.sfq.impl.Employee;
import com.sfq.impl.IVisitor;
public class Manager extends Employee {private String performance;public String getPerformance() {return performance;}public void setPerformance(String performance) {this.performance = performance;}@Overridepublic void accept(IVisitor visitor) {visitor.visit(this);}
}
★ 定义IVisitor接口,设置访问的类。
import com.sfq.action.CommonEmployee;
import com.sfq.action.Manager;
public interface IVisitor {//定义可以访问的对象public void visit(CommonEmployee commonEmployee);public void visit(Manager manager);
}
★ 定义一个真实的访问者,实现了IVisitor接口,并定义所要产生的逻辑结构。
import com.sfq.impl.Employee;
import com.sfq.impl.IVisitor;
public class Visitor implements IVisitor {//打印普通员工报表@Overridepublic void visit(CommonEmployee commonEmployee) {System.out.println(getCommonEmployee(commonEmployee));}//打印部门经理报表@Overridepublic void visit(Manager manager) {System.out.println(getManagerInfo(manager));}//组装基本信息private String getBasicInfo(Employee employee) {String info = "姓名:" + employee.getName() + "\t";info = info + "性别:" + (employee.getSex() == employee.FEMALE?"女":"男") + "\t";info = info + "薪水:" + employee.getSalary();return info;}//组装部门经理信息private String getManagerInfo(Manager manager) {String basicInfo = this.getBasicInfo(manager);String otherInfo = "业绩:" + manager.getPerformance() + "\t";return basicInfo + otherInfo;}//组装普通员工信息private String getCommonEmployee(CommonEmployee commonEmployee) {String basicInfo = this.getBasicInfo(commonEmployee);String otherInfo = "工作:" + commonEmployee.getJob() + "\t";return basicInfo + otherInfo;}
}
★ 在场景类中进行员工信息组装和打印
import java.util.ArrayList;
import com.sfq.action.CommonEmployee;
import com.sfq.action.Manager;
import com.sfq.action.Visitor;
import com.sfq.impl.Employee;
public class Client {public static void main(String[] args) {for(Employee emp:mockEmployee()) {emp.accept(new Visitor());}}public static ArrayList<Employee> mockEmployee() {ArrayList<Employee> empList = new ArrayList<Employee>();//员工CommonEmployee zhangSan = new CommonEmployee();zhangSan.setJob("蓝领、苦工、搬运工!");zhangSan.setName("张三");zhangSan.setSalary(1000);zhangSan.setSex(Employee.MALE);empList.add(zhangSan);//经理Manager liSi = new Manager();liSi.setPerformance("业绩为负,全靠舔!");liSi.setName("李四");liSi.setSalary(10000);liSi.setSex(Employee.MALE);empList.add(liSi);return empList;}
}结果
姓名:张三 性别:男 薪水:1000工作:蓝领、苦工、搬运工!
姓名:李四 性别:男 薪水:10000业绩:业绩为负,全靠舔!
场景类中的mockEmployee()方法,实际上就是我们通用模型中的结构对象ObjectStruture。
如果想要修改报表格式,只需要再产生一个IVisitor的实现类就可以了。如果使用Spring开发,并且在Spring的配置文件中使用接口注入,则只需要把配置文件中的 ref 修改即可。
(2)统计功能
处理数据库中上亿条的数据,必须使用存储过程来处理,如果使用应用服务器,则会导致数据库连接始终处于100%占用状态。但对于少量的数据的统计和报表的批处理通过访问者模式来处理会比较简单。如下,统计一下公司人员的工资总额。
★ 在IVisitor接口中加入求工资总和的方法getTotalSalary()。
import com.sfq.action.CommonEmployee;
import com.sfq.action.Manager;
public interface IVisitor {//定义可以访问的对象public void visit(CommonEmployee commonEmployee);public void visit(Manager manager);public int getTotalSalary();
}
★ 在Visitor中分别定义普工和经理的工资系数,根据实际工资求得各自的工资,然后实现getTotalSalary()方法
public class Visitor implements IVisitor {//部门经理工资系数private final static int MANAGER_COEFFICIENT = 5;//员工工资系数private final static int COMMONEMPLOYEE_COEFFICIENT = 2;//经理工资总和private int managerTotalSalary = 0;//普通员工工资总和private int commonTotalSalary = 0; //所有员工工资总和@Overridepublic int getTotalSalary() { return this.commonTotalSalary + this.managerTotalSalary;}//计算普工工资总和private void calCommonSalary(int salary) {this.commonTotalSalary = this.commonTotalSalary + salary * COMMONEMPLOYEE_COEFFICIENT;}//计算经理工资总和private void calManagerSalary(int salary) {this.managerTotalSalary = this.managerTotalSalary + salary * MANAGER_COEFFICIENT;}
}
★ 在场景类中调用即可。当然,这个程序是需要结合数据库才能计算所有员工工资的。
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());}
}
(3)多个访问者
在实际的项目中,一个对象,多个访问者的情况非常多。
通常情况下,报表分两种:
- 展示表:通过数据库查询,把结果展示出来;
- 汇总表:需要通过模型或者公式计算,一般都是批处理,这类似于计算工资总额;
这两种报表格式是对同一堆数据的两种处理方式。从程序上看,一个类就有了不同的访问者了。
★ 展示表接口:
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.getSalary()*COMMONEMPLOYEE_COEFFICIENT; }//访问部门经理,计算工资总额 public void visit(Manager manager) { this.managerTotalSalary = this.managerTotalSalary + manager.getSalary()*MANAGER_COEFFICIENT ; }
}
★ 两个访问者的调用
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(); }
}
(4)双分派
双分派(double dispatch)问题:双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型。
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 abstract class AbsActor { //演员都能够演一个角色 public void act(Role role){ System.out.println("演员可以扮演任何角色"); }//可以演功夫戏 public void act(KungFuRole role){ System.out.println("演员都可以演功夫角色"); }
}public class YoungActor extends AbsActor { //年轻演员最喜欢演功夫戏 public void act(KungFuRole role){ System.out.println("最喜欢演功夫角色"); }
}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(); //开始演戏 role.accept(actor);}
}结果
年龄大了,不能演功夫角色
不管演员类和角色类怎么变化,我们都能够找到期望的方法运行,这就是双分派。
设计模式——访问者模式相关推荐
- 设计模式 访问者模式
文章目录 访问者模式 访问者模式实战 访问者模式 在相同的数据结构下, 增加容易变化的业务访问逻辑, 为了增强扩展性, 将易变的访问逻辑进行解耦的一种设计模式. 访问者模式实战 模拟学校中, 有老师和 ...
- 设计模式---访问者模式
访问者模式 介绍 定义及使用场景 UML类图 角色 财务案例 个人心得体会 静态分派以及动态分派 静态分派 动态分派 访问者模式中的伪动态双分派 对访问者模式的一些思考 总结 优点 缺点 适用性 参考 ...
- C++设计模式——访问者模式(visitor pattern)
一.原理讲解 1.1意图 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 1.2应用场景 一个对象结构包含很多类对象,它们有不同的接口,而你 ...
- java设计模式---访问者模式
Java深入到一定程度,就不可避免的碰到设计模式这一概念,了解设计模式,将使自 己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广 泛,遵循一定的编程模式,才能使自 ...
- yii2通过url访问类中的方法_行为型设计模式 访问者模式
author zong email zongzhe1996@163.com 介绍 在访问者模式中,通过使用一个访问者类,可以改变元素类(被访问者)的执行算法.元素类的执行算法可以随着访问者的改变而改变 ...
- C++设计模式-访问者模式
目录 基本概念 代码与实例 基本概念 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变元素的类的前提下定义作用于这些元素的新操作. UML图如下(此图来源于大 ...
- PHP设计模式——访问者模式
声明:本系列博客参考资料<大话设计模式>,作者程杰. 访问者模式表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作. UML类图: 角色 ...
- C#设计模式——访问者模式(Vistor Pattern)
一.引言 在上一篇博文中分享了责任链模式,责任链模式主要应用在系统中的某些功能需要多个对象参与才能完成的场景.在这篇博文中,我将为大家分享我对访问者模式的理解. 二.访问者模式介绍 2.1 访问者模式 ...
- 大话设计模式—访问者模式
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法.通过这种方式,元素的执行算法可以随着访问者改变而改变.这种类型的设计模式属于行为型模式.根据模式,元 ...
- C++设计模式——访问者模式
访问者模式 在GOF的<设计模式:可复用面向对象软件的基础>一书中对访问者模式是这样说的:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的 ...
最新文章
- 频繁模式(frequent pattern)
- py3Dmol 简介、安装与入门
- sql server insert values 多值 与oracle 的不同
- Style Intelligence 10特点之用户自定义报表
- 豆瓣FLASHMP3播放器
- 最流行的python爬虫框架_Python最火爬虫框架Scrapy入门与实践
- python 字符串%和format_python基础任务二
- jquery元素节点操作
- resolv.conf
- python 小海龟鼠标画图_Python小海龟画图
- 用计算机玩游戏最简单的方法,如何制作电脑简易命令小游戏
- shiro 实现自己定义权限规则校验
- Java 的 ArrayList 的底层数据结构
- OFFICE2007 自编宏使用 以及 文件未找到 VBA6.DLL 错误处理
- 设计模式之抽象工厂模式以及与工厂模式区别
- VBS命令:关于以管理员身份运行程序的VBS命令
- 找到解决办法了,特回来写总结,the import cannot be resolved问题可以通过以下方法解决
- CAD参数绘制直线(网页版)
- axios.post发送小数据可以,但发送大数据出现Cross origin requests are only supported for protocol schemes: http, data,
- 删除播放器上的Flowplayer图标
热门文章
- 压缩气体储能领域新势力「嘉泰新能」获AC资本天使轮投资!
- 源码分析 --- MapReduce如何确定任务的SplitSize、Splits、Map、Reduce
- u-boot 顶层Makefile 分析
- 学习php开发难吗,PHP开发自学难吗,PHP自学要多长时间?
- godot 外部编辑器配置
- 喵懂区块链21期 | Monoxide:在突破不可能三角的边缘试探?
- Android值Intent匹配规则挖掘(PMS获取系统apk信息过程)
- BUUCTF——CRYPTO(记录不熟悉的题)(4)
- Mac苹果键盘多个按键没响应该如何解决呢
- 正弦余弦指引的乌鸦搜索算法-附代码