基本概念
访问者设计模式(visitor pattern) 是一种将数据操作和数据结构分离的设计模式。封装一些作为与某种数据结构的各元素操作,他可以在不改变数据结构的前提下定义作为元素的新操作。
核心原理是被访问的类里面加一个对外提供接待访问者的接口
访问者模式主要使用场景,在需要对一个对象里的属性进行很多不同操作,且操作彼此没有关联,而且需要避免各操作之间污染该类,可以考虑使用访问者模式。访问者模式是一种很复杂的设计模式,通常情况一般不需要使用,使用场景也不多,在使用访问者模式的情况需要先考虑清楚是否需要使用到该设计模式。
UML
角色分析
Visitor 抽象访问者接口或者抽象类 主要声明了每一个Element的操作行为
ConcreteVisitor 具体实现访问者 需要给每一个Element定义访问时候产生的行为
Element 元素接口或者抽象类 定义了接受访问者accept方法,接受一个访问者对象
ConcreteElement 元素的具体实现
ObjectStructure 定义对象接口 管理元素集合提供高层接口用来让访问者访问元素
代码实现
网上找到一个例子非常符合使用这种设计模式的场景
参考https://www.jianshu.com/p/1f1049d0a0f4
CEO和CTO员工考评,员工分别工程师和产品经理,CTO关注工程师的代码量,产品经理的产品个数;CEO关注的工程师的KPI和产品经理的KPI和产品数量,CEO和CTO对于不同的员工关注点不同。需要对不同的员工类型进行不同的处理,这时就可以使用访问者模式。
// 抽象 element 元素
@Getter
public abstract class Staff {
private final String name;
private final Integer kpi;
public Staff(String name) {
this.name = name;
this.kpi = ThreadLocalRandom.current().nextInt(10);
}
// 核心方法accpet 方法 接口不同的访问者
public abstract void accept(Visitor visitor);
}
// 具体的element concreteElement
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);
}
}
// 抽象visitor 类 使用2个不同的类型staff 无需在方法 if instance 判断
public interface Visitor {
void visit(Engineer engineer);
void visit(Manager manager);
}
// 具体的visitor 实现类 各自实现自己关注的逻辑
public class CEOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("CTO关注" + engineer.getName() + "KPI " + engineer.getKpi());
}
@Override
public void visit(Manager manager) {
System.out.println("CTO关注" + manager.getName() + "的KPI和产品数量 " + manager.getProducts() + " " + manager.getProducts());
}
}
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("CTO关注" + engineer.getName() + "的代码量" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
System.out.println("CTO关注" + manager.getName() + "的产品数" + manager.getProducts());
}
}
// ObjectStucture 数据聚合类
public class Report {
List<Staff> staffs = new ArrayList<>();
public Report() {
staffs.add(new Manager("经理-A"));
staffs.add(new Engineer("工程师-A"));
staffs.add(new Engineer("工程师-B"));
staffs.add(new Engineer("工程师-C"));
staffs.add(new Manager("经理-B"));
staffs.add(new Engineer("工程师-D"));
}
// 根据不同visitor 处理不同的逻辑
public void showReport(Visitor visitor){
for (Staff staff : staffs) {
staff.accept(visitor);
}
}
}
// 测试使用
public class VisitorTest {
public static void main(String[] args) {
Report report = new Report();
report.showReport(new CTOVisitor());
System.out.println("===========================================");
report.showReport(new CEOVisitor());
}
}
CTO关注经理-A的产品数8
CTO关注工程师-A的代码量71640
CTO关注工程师-B的代码量23516
CTO关注工程师-C的代码量88173
CTO关注经理-B的产品数8
CTO关注工程师-D的代码量35671
===========================================
CTO关注经理-A的KPI和产品数量 8 5
CTO关注工程师-AKPI 1
CTO关注工程师-BKPI 8
CTO关注工程师-CKPI 1
CTO关注经理-B的KPI和产品数量 6 3
CTO关注工程师-DKPI 6
使用细节
- 访问者模式实现了对数据操作和数据结构的解耦,拓展性强,灵活性高
- 对功能进行统一,可以做报表UI 拦截器 过滤器适用于数据结构相对稳定的系统
- 使用访问者模式使得系统更加复杂,具体元素修改的时候成本大
- 元素对访问者暴露细节 违反了迪米特原则