访问者模式介绍
最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。
访问者模式是一种将数据操作和数据结构分离的设计模式。(觉得太抽象,可以看下面的例子)。
访问者模式的使用场景
- 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
角色介绍
- Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
- ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
- Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
- ElementA、ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
package com.lzhsite.technology.designPattern.visitor.StaffDemo; /** * * 年底,CEO和CTO开始评定员工一年的工作绩效,员工分为工程师和经理, * CTO关注工程师的代码量、经理的新产品数量;CEO关注的是工程师的KPI和经理的KPI以及新产品数量。 * 由于CEO和CTO对于不同员工的关注点是不一样的,这就需要对不同员工类型进行不同的处理。 * 访问者模式此时可以派上用场了。 * @author lzhcode * */ public class Client { public static void main(String[] args) { // 构建报表 BusinessReport report = new BusinessReport(); System.out.println("=========== CEO看报表 ==========="); report.showReport(new CEOVisitor()); System.out.println("=========== CTO看报表 ==========="); report.showReport(new CTOVisitor()); } }
package com.lzhsite.technology.designPattern.visitor.StaffDemo; import java.util.Random; //员工基类 public abstract class Staff { public String name; public int kpi;// 员工KPI public Staff(String name) { this.name = name; kpi = new Random().nextInt(10); } // 核心方法,接受Visitor的访问 public abstract void accept(Visitor visitor); }
package com.lzhsite.technology.designPattern.visitor.StaffDemo; import java.util.Random; //经理 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); } }
package com.lzhsite.technology.designPattern.visitor.StaffDemo; public interface Visitor { // 访问工程师类型 void visit(Engineer engineer); // 访问经理类型 void visit(Manager manager); }
public class ReportUtil { public void visit(Staff staff) { if (staff instanceof Manager) { Manager manager = (Manager) staff; System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi + ", 新产品数量: " + manager.getProducts()); } else if (staff instanceof Engineer) { Engineer engineer = (Engineer) staff; System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi); } } }
package com.lzhsite.technology.designPattern.visitor.StaffDemo; public class CEOVisitor implements Visitor { @Override public void visit(Engineer engineer) { System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi); } @Override public void visit(Manager manager) { System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi + ", 新产品数量: " + manager.getProducts()); } }
在CEO的访问者中,CEO关注工程师的 KPI,经理的 KPI 和新产品数量,
通过两个 visitor 方法分别进行处理。如果不使用 Visitor 模式,只通过一个 visit 方法进行处理,
那么就需要在这个 visit 方法中进行判断,然后分别处理,代码大致如下:
这就导致了 if-else 逻辑的嵌套以及类型的强制转换,难以扩展和维护,当类型较多时,
这个 ReportUtil 就会很复杂。而使用 Visitor 模式,通过同一个函数对不同对元素类型进行相应对处理,
使结构更加清晰、灵活性更高。
package com.lzhsite.technology.designPattern.visitor.StaffDemo; public class CTOVisitor implements Visitor { @Override public void visit(Engineer engineer) { System.out.println("工程师: " + engineer.name + ", 代码行数: " + engineer.getCodeLines()); } @Override public void visit(Manager manager) { System.out.println("经理: " + manager.name + ", 产品数量: " + manager.getProducts()); } }
总结
我们要根据具体情况来评估是否适合使用访问者模式,例如,我们的对象结构是否足够稳定,是否需要经常定义新的操作,使用访问者模式是否能优化我们的代码,而不是使我们的代码变得更复杂。
- 访问者模式的优点。
- 各角色职责分离,符合单一职责原则
通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。 - 具有优秀的扩展性
如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。 - 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦。 - 灵活性
- 访问者模式的缺点。
- 具体元素对访问者公布细节,违反了迪米特原则
CEO、CTO需要调用具体员工的方法。 - 具体元素变更时导致修改成本大
变更员工属性时,多个访问者都要修改。 - 违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象
访问者 visit 方法中,依赖了具体员工的具体方法。
示例代码: