真刀实枪之访问者模式
- 从员工的隐私说起
- 通过组合模式可以把整个员工的人员信息都整合起来形成一张企业员工信息树
- 通过迭代器模式可以遍历公司所有的员工
-
现在要把公司的所有人员的信息都汇总起来,那该怎么办,作为一名逻辑工作者,当然先开始设计啦
- 类图
-
类图有了,先按照类图来实现一下吧!
-
Employee
package com.peng.fwz; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public abstract class Employee { // 0代表男性 public final static int MALE = 0; // 1代表女性 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 final void report() { String info = "名字:" + this.name + ",薪水:" + this.salary + ",性别:" + this.sex; // 获得员工的其他信息 info += this.getOtherInfo(); System.out.println(info); } // 拼装员工的其他信息 protected abstract String getOtherInfo(); }
-
CommonEmployee
package com.peng.fwz; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class CommonEmployee extends Employee { // 工作 private String job; public String getJob() { return job; } public void setJob(String job) { this.job = job; } @Override protected String getOtherInfo() { return ",工作:"+this.job; } }
-
Manager
package com.peng.fwz; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class Manager extends Employee { // 职责 private String performance; public String getPerformance() { return performance; } public void setPerformance(String performance) { this.performance = performance; } @Override protected String getOtherInfo() { return ",业绩:" + this.performance; } }
-
Client
package com.peng.fwz; import java.util.ArrayList; import java.util.List; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class Client { public static void main(String[] args) { for (Employee e : mockEmployee()) { e.report(); } } // 模拟公司员工情况 public static List<Employee> mockEmployee() { List<Employee> empList = new ArrayList<Employee>(); // 生产张三这个员工 CommonEmployee zhangSan = new CommonEmployee(); zhangSan.setJob("编写java程序~~"); zhangSan.setName("张三"); zhangSan.setSalary(66666); zhangSan.setSex(Employee.MALE); empList.add(zhangSan); // 生产李四这个员工 CommonEmployee liSi = new CommonEmployee(); liSi.setJob("页面美工~~"); liSi.setName("李四"); liSi.setSalary(22222); liSi.setSex(Employee.FEMALE); empList.add(liSi); // 生产一个经理 Manager wangWu = new Manager(); wangWu.setPerformance("管理项目~~"); wangWu.setName("王五"); wangWu.setSalary(88888); wangWu.setSex(Employee.MALE); empList.add(wangWu); // 返回该列表 return empList; } }
-
执行结果
名字:张三,薪水:66666,性别:0,工作:编写java程序~~ 名字:李四,薪水:22222,性别:1,工作:页面美工~~ 名字:王五,薪水:88888,性别:0,业绩:管理项目~~
-
- 类图
- 结果出来了,不过有点问题
- 各个部门的级别不同,管理的人眼也就不同,没有必要都把所有的人员的信息都看一遍
- 这个报表可能会改,展现形式也有可能改变--按照开闭原则,report方法已经写死,不太符合,子类不能自行修改
- 遇到问题就改呗,先从设计图纸下手
- 类图
- 类图
- 类图改好了,如何能为每个子类订制报表的方法呢?report方法是否可以提取到另外一个类Visitor中来实现,继续修改类图
- 类图
- 类图
- 两个子类的report方法都不需要了,感觉把方法委托出去了一样,但这与委托相去甚远,详细类图继续改造
- 类图
- 类图
-
在抽象类Employee类中增加了一个accept方法,该方法是一个抽象方法,由子类实现,其意义就是说我这类可以允许谁可以访问。在具体的实现类中调用访问者的方法
-
代码
-
IVisitor
package fwz2; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public interface IVisitor { // 可以访问普通员工 public void visit(CommonEmployee commonEmployee); // 可以访问管理员 public void visit(Manager manager); }
-
Visitor
package fwz2; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class Visitor implements IVisitor { // 访问普通员工--打印出报表 @Override public void visit(CommonEmployee commonEmployee) { System.out.println(this.getCommonEmployee(commonEmployee)); } // 访问管理者,打印出报表 @Override public void visit(Manager manager) { System.out.println(this.getManager(manager)); } // 组装出基本信息 private String getBasicInfo(Employee employee) { String info = "名字:" + employee.getName() + ",薪水:" + employee.getSalary() + ",性别:" + (employee.getSex() == Employee.FEMALE ? "女" : "男"); return info; } // 组装出部门经理信息 private String getManager(Manager manager) { String basicInfo = this.getBasicInfo(manager); String otherInfo = "业绩:" + manager.getPerformance(); return basicInfo + otherInfo; } // 组装出普通员工信息 private String getCommonEmployee(CommonEmployee commomEmployee) { String basicInfo = this.getBasicInfo(commomEmployee); String otherInfo = "业绩:" + commomEmployee.getJob(); return basicInfo + otherInfo; } }
-
Employee
package fwz2; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public abstract class Employee { // 0代表男性 public final static int MALE = 0; // 1代表女性 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); }
-
CommonEmployee
package fwz2; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class CommonEmployee extends Employee { // 工作 private String job; public String getJob() { return job; } public void setJob(String job) { this.job = job; } @Override public void accept(IVisitor visitor) { visitor.visit(this); } }
-
Manager
package fwz2; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class Manager extends Employee { // 职责 private String performance; public String getPerformance() { return performance; } public void setPerformance(String performance) { this.performance = performance; } @Override public void accept(IVisitor visitor) { visitor.visit(this); } }
-
Client
package fwz2; import java.util.ArrayList; import java.util.List; /** * @author kungfu~peng * @data 2017年11月27日 * @description */ public class Client { public static void main(String[] args) { for (Employee e : mockEmployee()) { e.accept(new Visitor()); } } // 模拟公司员工情况 public static List<Employee> mockEmployee() { List<Employee> empList = new ArrayList<Employee>(); // 生产张三这个员工 CommonEmployee zhangSan = new CommonEmployee(); zhangSan.setJob("编写java程序~~"); zhangSan.setName("张三"); zhangSan.setSalary(66666); zhangSan.setSex(Employee.MALE); empList.add(zhangSan); // 生产李四这个员工 CommonEmployee liSi = new CommonEmployee(); liSi.setJob("页面美工~~"); liSi.setName("李四"); liSi.setSalary(22222); liSi.setSex(Employee.FEMALE); empList.add(liSi); // 生产一个经理 Manager wangWu = new Manager(); wangWu.setPerformance("管理项目~~"); wangWu.setName("王五"); wangWu.setSalary(88888); wangWu.setSex(Employee.MALE); empList.add(wangWu); // 返回该列表 return empList; } }
-
执行结果
名字:张三,薪水:66666,性别:男业绩:编写java程序~~ 名字:李四,薪水:22222,性别:女业绩:页面美工~~ 名字:王五,薪水:88888,性别:男业绩:管理项目~~
-
-
- 首先看看IVisitor这个类,它是可以控制报表格式的,而其他类都不用修改【Spring中的接口注入】
访问者模式的定义
- Visitor Pattern
- Respresent an operation to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the element on witch it operates.(封装一些作用于某种数据结构中的各元素的操作,它可以不在改变数据结构的前提下定义作用于这些元素的新的操作)
访问者模式的通用类图
- 解释
- Visitor抽象访问者
- ConcreteVisitor具体访问者
- Element抽象元素
- ConcreteElement具体元素
- ObjectElement结构对象--【List、Set、Map】
访问者模式的通用代码--类图有了,例子也有了,还不快自己动手试试~~
- 注意事项:ObjectElement:用容器来装数据即可,别想那么复杂
访问者模式的应用
- 访问者模式的优点
- 符合单一的原则
- 优秀的扩展性
- 灵活性非常高
- 访问者模式的缺点
- 具体元素对访问者公布细节
- 具体元素变更比较困难
- 违背了依赖倒置的原则【访问者依赖的是具体元素,而不是抽象元素,这就破坏了依赖倒置原则】
- 访问者模式的使用场景
- 一个对象结构包含很对类对象,他们都由不同的接口,而你相对这些对象实施一些依赖于具体类的操作,也就是说迭代器已经不能胜任的事情
- 需要对一个对象结构中的对象进行很多不同并且不相关的操作,而避免让这些操作“污染”这些对象的类【访问者模式是对迭代器模式的扩充,可以遍历不同的对象,针对的对象不同,然后执行的操作也不同】
- 访问者模式还有一个用途:充当拦截器(Interceptor)角色--这将在混编模式中讲解
访问者模式的扩展
- 统计功能:数据统计+报表+批处理
- 类图简单演示
- 类图简单演示
- 多个访问者:展示表+汇总表
- 类图简单演示
- 类图简单演示
双分派[Double Dispatch]
- 单分派【Single Dispatch】:单分派语言处理一个操作是根据请求者的名称和接收到的参数决定的,在java中,有静态和动态绑定之说,它的实现是依据重载和覆写实现的
- 多分派【Mutilate Dispatch】
- 双分派:无论角色怎么变,都能够用同一个方法来执行【多分派的特例】
//核心:this这个类是Method方法的参数 public void accept(IObject obj){ obj.method(this); }
最佳实践
- 访问者模式是一种集中规整模式,特别适合大规模重构项目--功能集中化
- 还可以与其他模式混编建立一套自己的过滤器或拦截器
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;