访问者模式
-
定义:
提供一个作用于某种对象结构上的各元素的操作方式,可以使我们在不改变元素结构的前提下,定义作用于元素的新操作。如果系统的数据结构是比较稳定的,但其操作(算法)是易于变化的,那么使用访问者模式是个不错的选择;如果数据结构是易于变化的,则不适合使用访问者模式。
-
使用场景:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖与其具体类的操作,也就是用迭代器模式已经不能胜任的情景。
- 需要对一个对结构中的对象进行很多不同并且不相关的操作,而你想避免让这些操作“污染”这些对象。
-
UML:
- Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
- ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
- Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
- ElementA、ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。
-
优点:
- 各角色职责分离,符合单一职责原则。
- 具有优秀的扩展性。
- 使得数据结构和作用于结构上的操作解耦合,使得操作集合可以独立的变化。
- 提高了系统的灵活性。
-
缺点:
- 具体元素对访问者公开细节,违反了迪米特法则。
- 具体元素变更时修改成本大。
- 违反了依赖倒置原则,为了达到区别对待而依赖了具体类,没有依赖抽象。
-
样例:
// 员工基类 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); }
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); } }
// 员工业务报表类 public class BusinessReport { private List<Staff> mStaffs = new LinkedList<>(); public BusinessReport() { mStaffs.add(new Manager("经理-A")); mStaffs.add(new Engineer("工程师-A")); mStaffs.add(new Engineer("工程师-B")); mStaffs.add(new Engineer("工程师-C")); mStaffs.add(new Manager("经理-B")); mStaffs.add(new Engineer("工程师-D")); } /** * 为访问者展示报表 * @param visitor 公司高层,如CEO、CTO */ public void showReport(Visitor visitor) { for (Staff staff : mStaffs) { staff.accept(visitor); } } }
public interface Visitor { // 访问工程师类型 void visit(Engineer engineer); // 访问经理类型 void visit(Manager manager); }
// 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()); } }
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); } } }
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看报表 ==========="); report.showReport(new CEOVisitor()); System.out.println("=========== CTO看报表 ==========="); report.showReport(new CTOVisitor()); } }