(《设计模式解析与实战——何红辉,关爱民》读书笔记)
大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是真的需要它了。
一、定义
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
二、使用场景
(1)对象结构比较稳定,但经常需要在此对象结构上定义新的操作;
(2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
三、访问者模式的简单示例
比如,在业绩考核时,不同领域的管理人员对于员工的评定标准是不同的。这里管理层为CEO、CTO,员工为工程师、经理。CEO只关心员工的KPI(关键绩效指标(Key Performance Indicators,简称KPI),又称主要绩效指标、重要绩效指标、绩效评核指标等,是指衡量一个管理工作成效最重要的指标,是一项数据化管理的工具,必须是客观、可衡量的绩效指标),而CTO只关注工程师的代码量和经理的新产品数量。
/**
* 员工基类
*/
public abstract class Staff {
public String name;
// 员工KPI
public int kpi;
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
/**
* 接收Visitor的访问
*
* @param 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);
}
/**
* 工程师一年写的代码量
* @return 代码量
*/
public int getCodeLines() {
return new Random().nextInt(10 * 10000);
}
}
/**
* 经理
*/
public class Manager extends Staff{
// 产品数量
private int products;
public Manager(String name) {
super(name);
products = new Random().nextInt(10);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
/**
* 一年做的产品的数量
* @return 产品的数量
*/
public int getProducts() {
return products;
}
}
/**
* 访问接口
*/
public interface Visitor {
// 访问工程师类型
public void visit(Engineer engineer);
// 访问经理类型
public void visit(Manager manager);
}
/**
* 员工业务报表
*/
public class BusinessReport {
List<Staff> staffs = new LinkedList<>();
public BusinessReport() {
staffs.add(new Manager("某经理"));
staffs.add(new Engineer("工程师——1"));
staffs.add(new Engineer("工程师——2"));
staffs.add(new Engineer("工程师——3"));
}
/**
* 为访问者展示报表
* @param visitor 公司高层,如CEO、CTO
*/
public void showReport(Visitor visitor) {
for (Staff staff : staffs) {
staff.accept(visitor);
}
}
}
/**
* CEO访问者,只关注业绩
*/
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());
}
}
/**
* CTO访问者,只关注业绩
*/
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());
}
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
// 构建报表
BusinessReport report = new BusinessReport();
System.out.println("=====给CEO看的报表=====");
// 设置访问者,这里是CEO
report.showReport(new CEOVisitor());
System.out.println("\n=====给CTO看的报表=====");
// 注入另一个访问者
report.showReport(new CTOVisitor());
}
}
运行结果:
四、总结
优点:
(1)各角色职责分离,符合单一职责原则;
(2)具有优秀的扩展性;
(3)使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化;
(4)灵活性。
缺点:
(1)具体元素对访问者公布细节,违反了迪米特原则(一个对象应该对其他对象有最少的了解);
(2)具体元素变更时导致修改成本大;
(3)违反了依赖导致原则(一种特定的解耦形式,使得高层次的模块不依赖于低层次的模块的实现细节的目的),为了达到“区别对待”而依赖了具体类,没有依赖对象。