概述
访问者模式是一种将数据操作与数据结构分离的模式
是23中设计模式中最复杂的一个,使用频率也不是很高,但一旦需要它的时候将变得非常有用.
软件系统中拥有一个由许多对象构成的,比较稳定的对象结构.这些对象的类都拥有一个accept
方法用来接收访问者对象的访问.
访问者是一个接口,拥有visit
方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理.
在对象结构的一次访问过程中,我们遍历整个对象结构,对每个元素都实施accept
方法,在每个元素的accept
方法中会调用访问者的visit
方法,使得访问者得以处理对象结构的每一个元素.
定义
封装一些作用于某种数据结构中的各个元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作.
使用场景
- 对象结构比较稳定,但经常需要在此对象结构上定义新的操作.
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作
污染
这些对象本身的时候
UML类图
其中涉及到的角色介绍:
Visitor
: 接口或抽象类,它定义了对每一个元素(Element
)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素个数是一样的,因此,访问者模式要求元素的了类族要稳定.ConcreteVisitor
: 具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为Element
: 元素接口或者抽象类,它定义了一个接受访问者(accept
)的方法,其意义是指每一个元素都要可以被访问者访问.ElementAA,ElementB
: 具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法.ObjectStructure
: 定义当中所提到的数据结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问.
实例
访问者模式是一个结构,概念都较为复杂的设计模式,接下来我们用一个简单的实例来看一下其使用,
如下实例模拟了公司年终业绩考核问题,为了简单说明问题,把员工简单分为工程师和经理,评定员工分别为CEO
和CTO
,
假定CTO
之关注工程师代码质量和经理的新产品数量,而CEO
之关注工程师的KPI
和经理的KPI
以及新产品数量
- 首先定以员工类接口
public abstract class Staff {
public String name;
public int kpi;
public Staff(String _name) {
name = _name;
kpi = new Random().nextInt(10);
}
public abstract void accept(Visitor _visitor);
}
- 定义工程师和经理,继承与员工类
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 Engineer extends Staff {
public Engineer(String _name) {
super(_name);
}
@Override public void accept(Visitor _visitor) {
_visitor.visit(this);
}
public int getCoceLines(){
return new Random().nextInt(10*10000);
}
}
定义
Visitor
接口public interface Visitor { void visit(Engineer _engineer); void visit(Manager _manager); }
定义具体的
Visitor
,实现Visitor
接口,public class CEOVisitor implements Visitor { private static final String TAG = "CEOVisitor"; @Override public void visit(Engineer _engineer) { Log.d(TAG, "visit: -->" + "工程师 : " + _engineer.name + ",kpi : " + _engineer.kpi); } @Override public void visit(Manager _manager) { Log.d(TAG, "visit: -->" + "经理 : " + _manager.name + ",kpi : " + _manager.kpi + ",新产品数 : " + _manager.getProducts()); } } //....
客户端调用
为了方便,这里加了一个报表类
public class BusinessReport { List<Staff> mStaffs = new LinkedList<>(); public BusinessReport() { mStaffs.add(new Manager("王经理")); mStaffs.add(new Engineer("工程师小刘")); //... } public void showReport(Visitor _visitor) { for (Staff staff : mStaffs) { staff.accept(_visitor); } } }
真正调用的时候
BusinessReport businessReport = new BusinessReport(); Log.d(TAG, "给CEO看报表 "); businessReport.showReport(new CEOVisitor()); Log.d(TAG, "给CTO看报表"); businessReport.showReport(new CTOVisitor());
优缺点
优点:
- 各个角色职责分离,符合单一职责原则.
- 优秀的扩展性
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
- 灵活性高
缺点
- 具体元素对访问者公布细节,违反了迪米特原则
- 具体元素变更时导致修改成本大
违反了依赖倒置原则,为了达到区别对待,而依赖了具体类,而不是抽象.
实例代码