访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式,是指封装一些作用于某些数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作,属于行为型模式
访问者模式被称为最复杂的设计模式,并且使用频率不高,基本思想是,针对系统中拥有固定类型的对象结构(元素),在其内提供一个accept()方法用来接受访问者对象的访问,不同的访问者对同一元素的访问内容不同,使得相同的元素集合可以产生不同的数据结果。accept()方法可以接收不同的访问者对象,然后在内部将自己(元素)转发到接收到的访问者对象的visit()方法内。访问者内部对应类型的visit()方法就会得到回调执行,对元素进行操作,也就是通过两次动态分发(第一次是对访问者的分发accept()方法,第二次是对元素的分发visit()方法),才最终将一个具体的元素传递到一个具体的访问者,如此一来,就解耦了数据结构与操作,且数据操作不会改变元素状态
访问者模式的核心是,解耦数据结构与数据操作,使得对元素的操作具备优秀的扩展性,可以通过扩展不同的数据操作类型(访问者)实现对相同元素集的不同的操作
应用场景
1、数据结构稳定,作用于数据结构的操作经常变化的场景
2、需要数据结构与数据操作分离的场景
3、需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景
角色
抽象访问者(Visitor):接口或抽象类,该类冠以了对每一个具体元素(Element)的访问行为visit()方法,其参数就是具体的元素(Element)对象。理论上来说,Visitor的方法个数与元素个数是相等的,如果元素个数经常变动,会导致Visitor的方法也要进行变动,此时,该情景并不适用访问者模式
具体访问者(ConcreteVisitor):实现对具体元素的操作
抽象元素(Element):接口或抽象类,定义了一个接受访问者访问的方法accept(),表示所有元素类型都支持被访问者访问
具体元素(ConcreteElement):具体元素类型,提供接受访问者的具体实现,通常的实现为:visitor.visit(this)
结构对象(ObjectStruture):该类内部维护了元素集合,并提供方法接受访问者对改集合所有元素进行操作
代码实现
抽象类
public abstract class Employee {
public String name;
public int kpi; //员工KPI
public Employee(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
//接收访问者的访问
public abstract void accept(IVisitor visitor);
}
public class Engineer extends Employee {
public Engineer(String name) {
super(name);
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
//考核指标是每年的代码量
public int getCodeLines(){
return new Random().nextInt(10* 10000);
}
}
public class Manager extends Employee {
public Manager(String name) {
super(name);
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
//考核的是每年新产品研发数量
public int getProducts(){
return new Random().nextInt(10);
}
}
public class BusinessReport {
private List<Employee> employees = new LinkedList<Employee>();
public BusinessReport() {
employees.add(new Manager("产品经理A"));
employees.add(new Engineer("程序员A"));
employees.add(new Engineer("程序员B"));
employees.add(new Engineer("程序员C"));
employees.add(new Manager("产品经理B"));
employees.add(new Engineer("程序员D"));
}
public void showReport(IVisitor visitor){
for (Employee employee : employees) {
employee.accept(visitor);
}
}
}
接口
public interface IVisitor {
void visit(Engineer engineer);
void visit(Manager manager);
}
public class CEOVistitor implements IVisitor {
public void visit(Engineer engineer) {
System.out.println("工程师" + engineer.name + ",KIP:" + engineer.kpi);
}
public void visit(Manager manager) {
System.out.println("经理:" + manager.name + ",KPI:" + manager.kpi + ",产品数量:" + manager.getProducts());
}
}
public class CTOVistitor implements IVisitor {
public void visit(Engineer engineer) {
System.out.println("工程师" + engineer.name + ",代码行数:" + engineer.getCodeLines());
}
public void visit(Manager manager) {
System.out.println("经理:" + manager.name + ",产品数量:" + manager.getProducts());
}
}
测试
public class Test {
public static void main(String[] args) {
BusinessReport report = new BusinessReport();
System.out.println("==========CEO看报表===============");
report.showReport(new CEOVistitor());
System.out.println("==========CTO看报表===============");
report.showReport(new CTOVistitor());
}
}
优点
1、解耦了数据结构与数据操作,使得操作集合可以独立变化
2、扩展性好:可以通过扩展访问者角色,实现对数据集的不同操作
3、元素具体类型并非单一,访问者均可操作
4、各角色职责分离,符合单一职责原则
缺点
1、无法增加元素类型:若系统数据结构对象易于变化,经常有新的数据对象增加进来,则访问者类必须增加对应元素类型的操作,违背了开闭原则
2、具体元素变得困难:具体元素增加属性,删除属性等操作会导致对应的访问者类需要进行相应的修改,尤其当有大量访问者类时,修改范围太大
3、违背依赖倒置原则:为了达到“区别对待”,访问者依赖的是具体元素类型,而不是抽象