定义
可作用于对象结构中各个元素,在不改变各元素类的前提下,定义作用于这些元素新操作方法的一种行为型设计模式。
主要组成
抽象访问者(Visitor): 声明出对对象结构中每一个具体元素的访问方法visit,传入Concrete Element对象作为参数
具体访问者(Concrete Visitor):
实现各种visit方法,调用具体元素对象完成对应的各种操作
元素(Element):
定义出抽象accept方法,用于接收Visitor对象参数
具体元素(Concrete Element):
实现accept操作,该操作一般用于操作执行Visitor.visit方法,将自身作为参数传入
对象结构(Object structure):
各个元素构成的一个整体,提供能够让访问者访问所有元素的接口。可以是集合(比如List),或者是复合的类对象。
UML图
框架代码
抽象访问者:
需要依赖于具体的元素类,而不是抽象元素类,以避免if-else之类的嵌套和类型判断
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
具体访问者:
public class ConcreteVisitorA implements Visitor{
@Override
public void visit(ConcreteElementA elementA) {
// use elementA to do something
}
@Override
public void visit(ConcreteElementB elementB) {
// use elementB to do something
}
}
public class ConcreteVisitorB implements Visitor{
@Override
public void visit(ConcreteElementA elementA) {
// use elementA to do something
}
@Override
public void visit(ConcreteElementB elementB) {
// use elementB to do something
}
}
抽象元素(可能接口或者抽象类):
由各个具体元素类实现accept接口,用于使得Visitor依赖于具体元素类
public interface Element {
void accept(Visitor visitor);
}
具体元素:
public class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Client中简单调用:
List<Element> elements = new ArrayList<>();
elements.add(new ConcreteElementA());
elements.add(new ConcreteElementB());
elements.add(new ConcreteElementA());
ConcreteVisitorA concreteVisitorA = new ConcreteVisitorA();
ConcreteVisitorB concreteVisitorB = new ConcreteVisitorB();
for (Element element : elements) {
element.accept(concreteVisitorA);
element.accept(concreteVisitorB);
}
具体例子
公司对员工进行考核,员工存在工程师和产品经理,考核的评审有CEO和CTO,CTO只关注工程师的代码量和产品经理的新产品数量。而CEO则关注工程师的KPI和产品经理的KPI及新产品数量。从这边可以看出CEO和CTO对员工考核的关注点不一样,这就需要对员工类型进行处理,就可以用到访问者模式(员工分类结构也是很稳定不变的)。(来自<<Android源码设计模式解析与实践>>)
UML
代码
员工Staff:
public abstract class Staff {
String name;
public Staff(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract int getKPI();
public abstract void accept(Visitor visitor);
}
工程师Engineer:
public class Engineer extends Staff{
public Engineer(String name) {
super(name);
}
@Override
public int getKPI() {
return 9;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getCodeLines() {
return 9999;
}
}
产品经理Manager:
public class Manager extends Staff{
public Manager(String name) {
super(name);
}
@Override
public int getKPI() {
return 10;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getProductCount() {
return 5;
}
}
访问者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("CEOVisitor 考核工程师"+ engineer.getName() + ",KPI:" + engineer.getKPI());
}
@Override
public void visit(Manager manager) {
// TODO Auto-generated method stub
System.out.println("CEOVisitor 考核产品经理"+ manager.getName() + ",KPI:" + manager.getKPI() + ",产品数:" + manager.getProductCount());
}
}
CTO:
public class CTOVisitor implements Visitor{
@Override
public void visit(Engineer engineer) {
System.out.println("CTOVisitor 考核工程师"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
// TODO Auto-generated method stub
System.out.println("CTOVisitor 考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
}
}
BussinessReport(对应Object structure):
public class BussinessReport {
List<Staff> staffs = new ArrayList<>();
public BussinessReport() {
staffs.add(new Engineer("小张"));
staffs.add(new Manager("小王"));
}
public void showReport(Visitor visitor) {
for (Staff staff : staffs) {
staff.accept(visitor);
}
}
}
Client调用:
//构建报表
BussinessReport bussinessReport = new BussinessReport();
//给CEO看
bussinessReport.showReport(new CEOVisitor());
//给CTO看
bussinessReport.showReport(new CTOVisitor());
假设不使用访问者模式
如果不使用访问者模式的话,也就不存在Visitor继承体系,以上述具体例子为例则需要对Staff的各种类型进行判断:
假设在ReportUtils处理:
public class ReportUtils {
public static void visitCEO(Staff staff) {
if (staff instanceof Manager) {
Manager manager = (Manager)staff;
System.out.println("考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
} else if(staff instanceof Engineer) {
Engineer engineer = (Engineer)staff;
System.out.println("考核工程师"+ engineer.getName() + ",KPI:" + engineer.getKPI());
}
}
public static void visitCTO(Staff staff) {
if (staff instanceof Manager) {
Manager manager = (Manager)staff;
System.out.println("考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
} else if(staff instanceof Engineer) {
Engineer engineer = (Engineer)staff;
System.out.println("考核工程师"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
}
}
}
这就会导致出现一大堆的if -else嵌套和类型转换,当类型较多的时候就会难以扩展和维护,并且当关注层面不同,重复的判断会很多,难以维护后续持续扩展新的操作(visitXX,visitXXX...)。而使用访问者Visitor模式,能够通过accept-visit方法的配套使用使得不同关注面的操作可以分离,并且去除一大堆嵌套if-else和类型转换的判断,灵活性更好,可扩展性高,更易于维护。
总结
优点
角色职责分离,符合单一职责原则;
允许对组合结构中的各个元素加入新的操作,而不需要修改结构本身,增加新操作相对容易;
集中管理访问者所进行操作的代码;
缺点
具体元素元素对访问者公布细节,违反了迪米特原则(一个类对自己需要耦合或调用的类知道的最少);
增加删减具体元素时修改成本过大,需要修改Visitor继承体系;
为了达到"区别对待"而依赖具体类而不是抽象,违反了"依赖倒置"原则(高层模块不应该依赖低层模块,两者都应该依赖抽象);
应用场景
1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
网友评论