以查看报表引入访问者模式
年底,CEO和CTO开始评定员工一年的工作绩效,员工分为工程师和经理。
- CTO关注工程师的代码量、经理的新产品数量。
- CEO关注的是工程师的KPI和经理的KPI以及新产品数量。
由于CEO和CTO对于不同员工的关注点是不一样的,这就需要对不同员工类型进行不同的处理。访问者模式此时可以派上用场了。
最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
访问者模式是一种将数据操作和数据结构分离的设计模式。
1)对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

员工基类,accept 方法表示接受访问者的访问,由子类具体实现。Visitor 是个接口,传入不同的实现类,可访问不同的数据。
- // 员工基类
- // 提取经理和工程师的共同特征
- public abstract class Staff {
-
- public String name;
- public int kpi;
-
- public Staff(String name) {
- this.name = name;
- this.kpi = new Random().nextInt(10);
- }
-
- // 核心方法,接受Visitor的访问
- public abstract void accept(Visitor visitor);
- }
工程师和产品经理
- public class Engineer extends Staff{
-
- public Engineer(String name){
- super(name);
- }
-
- @Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
- }
-
- // 获取代码量
- public int getCodeLines(){
- return new Random().nextInt(10 * 10000);
- }
- }
- public class Manager extends Staff{
-
- public Manager(String name){
- super(name);
- }
-
- @Override
- public void accept(Visitor visitor) {
- visitor.visit(this);
- }
-
- //获取产品数量
- public int getProducts(){
- return new Random().nextInt(10);
- }
- }
工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为差异性,才使得访问模式能够发挥它的作用。Staff、Engineer、Manager 3个类型就是对象结构,这些类型相对稳定,不会发生变化。
然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的 showReport 方法查看所有员工的业绩,具体代码如下:
- public class BusinessReport {
-
- private List
list = new ArrayList(); -
- // 添加员工
- public void addStaff(Staff staff){
- list.add(staff);
- }
-
- //删除员工
- public void removeStaff(Staff staff){
- list.remove(staff);
- }
-
- // 展示报表
- public void show(Visitor visitor){
- for (Staff staff : list) {
- staff.accept(visitor);
- }
- }
- }
下面看看 Visitor 类型的定义, Visitor 声明了两个 visit 方法,分别是对工程师和经理对访问函数,也就是说对于工程师和经理的访问会调用两个不同的方法,以此达成区别对待、差异化处理。
- public interface Visitor{
- // 访问工程师类型
- void visit(Engineer engineer);
-
- // 访问经理类型
- void visit(Manager manager);
- }
CEO和CTO
- public class CEO implements Visitor {
- public void visit(Engineer engineer) {
- System.out.println(engineer.name + "的KPI ===> " + engineer.kpi);
- }
-
- public void visit(Manager manager) {
- System.out.println(manager.name + "的KPI ===> " + manager.kpi
- + " 产品数量 ===> " + manager.getProducts());
- }
- }
- public class CTO implements Visitor {
- public void visit(Engineer engineer) {
- System.out.println(engineer.name + "的代码量 ===> " + engineer.getCodeLines());
- }
-
- public void visit(Manager manager) {
- System.out.println(manager.name + "产品数量 ===> " + manager.getProducts());
- }
- }
如果不使用 Visitor 模式,只通过一个 visit 方法进行处理,那么就需要在这个 visit 方法中进行判断,然后分别处理,这就导致了 if-else 逻辑的嵌套以及类型的强制转换,难以扩展和维护。
下面是客户端代码
- public class Client {
- public static void main(String[] args) {
- BusinessReport businessReport = new BusinessReport();
- businessReport.addStaff(new Engineer("工程师1"));
- businessReport.addStaff(new Engineer("工程师2"));
- businessReport.addStaff(new Engineer("工程师3"));
- businessReport.addStaff(new Engineer("工程师4"));
- businessReport.addStaff(new Engineer("工程师5"));
- businessReport.addStaff(new Manager("产品经理1"));
- businessReport.addStaff(new Manager("产品经理2"));
- businessReport.addStaff(new Manager("产品经理3"));
-
- System.out.println("-----CEO看报表-----");
- businessReport.show(new CEO());
- System.out.println("-----CTO看报表-----");
- businessReport.show(new CTO());
- }
- }
输出
- -----CEO看报表-----
- 工程师1的KPI ===> 6
- 工程师2的KPI ===> 7
- 工程师3的KPI ===> 5
- 工程师4的KPI ===> 7
- 工程师5的KPI ===> 1
- 产品经理1的KPI ===> 9 产品数量 ===> 2
- 产品经理2的KPI ===> 1 产品数量 ===> 1
- 产品经理3的KPI ===> 9 产品数量 ===> 7
- -----CTO看报表-----
- 工程师1的代码量 ===> 37256
- 工程师2的代码量 ===> 70150
- 工程师3的代码量 ===> 7235
- 工程师4的代码量 ===> 37880
- 工程师5的代码量 ===> 55440
- 产品经理1产品数量 ===> 8
- 产品经理2产品数量 ===> 2
- 产品经理3产品数量 ===> 8
在上述示例中,Staff 扮演了 Element 角色,而 Engineer 和 Manager 都是 ConcreteElement;CEOVisitor 和 CTOVisitor 都是具体的 Visitor 对象;而 BusinessReport 就是 ObjectStructure;Client就是客户端代码。
访问者模式最大的优点就是增加访问者非常容易,我们从代码中可以看到,如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到数据对象与数据操作相分离的效果。
我们要根据具体情况来评估是否适合使用访问者模式,例如,我们的对象结构是否足够稳定,是否需要经常定义新的操作,使用访问者模式是否能优化我们的代码,而不是使我们的代码变得更复杂。
1、各角色职责分离,符合单一职责原则
通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。
2、具有优秀的扩展性
如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。
3、使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦
4、灵活性
1、具体元素对访问者公布细节,违反了迪米特原则
CEO、CTO需要调用具体员工的方法。
2、具体元素变更时导致修改成本大
变更员工属性时,多个访问者都要修改。
3、违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象
访问者 visit 方法中,依赖了具体员工的具体方法。