一、介绍,定义
软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个visit方法,这个方法对访问的对象结构中不同类型的元素作出不同的处理 。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中会调用访问者的visit方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。
封装一些 作用于某种数据结构中的各种元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
二、使用场景
1.对象结构比较稳当,但经常需要在此对象结构上定义新的操作。
2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。
三、UML类图
22.png角色介绍
Visitor:接口或者抽象类,它定义了对每个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上与元素个数是一样的。因此,访问者模式要求元素的类族要稳定,如果经常添加、移除元素类,必然会频繁修改Visitor接口,如果出现这种情况,则说明不适合使用访问者模式。
ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
Element:元素接口或者抽象类,它提供接受访问者(accept)方法,其意义是指每一个元素都要可以被访问者访问。
ElementA、ElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素的方法。
ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象描述,它内部管理了元素的集合,并且可以迭代这些元素供访问者访问。
四、简单实现
年终业绩考核时,员工分为工程师和经理,评定员工的分为CEO和CTO,CTO只关注工程师的代码量、经理的新产品数量;CEO只关心工程师的KPI和经理的KPI以及新产品数量,从中可以看出对不同员工关注点是不同的,这就需要对不同员工不同处理。
/* 员工基类
* Staff类定义了员工的基本信息以及一个accept方法,accept方法表示接受访问者的访问,由子类具体实现。
*/
public abstract class Staff {
public String name;
public int kpi; //员工kpi
public Staff(String aName) {
this.name = aName;
kpi = new Random().nextInt(10);
}
//接受Visitor的访问
public abstract void accept(Visitor visitor);
}
工程师和经理类
/**
* 工程师类中添加了获取代码行数的函数,而经理类型中则添加了获取新产品数量的函数,
* 它们的职责是不一样的,也正是它们的差异性,才使得访问者模式能够发挥它是作用。
* Staff、Engineer、Manager3个类型就是对象结构,这些类型相对稳定,不会发生变化。
*/
public class Engineer extends Staff {
public Engineer(String aName) {
super(aName);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//工程师这一年写的代码数量
public int getCodeLines() {
return new Random().nextInt(10*10000);
}
}
public class Manager extends Staff {
private int products;// 产品数量
public Manager(String aName) {
super(aName);
products = new Random().nextInt(10);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//一年内做的产品数量
public int getProducts() {
return products;
}
}
public interface Visitor {
//访问工程师类型
public void visit(Engineer engineer);
//访问经理类型
public void visit(Manager manager);
}
然后将这些员工添加到一个业务报表类中,公司高层通过该报表类的showReport函数查看所有员工的业绩
public class BusinessReport {
List<Staff> mStaffs = new LinkedList<Staff>();
public BusinessReport() {
mStaffs.add(new Manager("manager-1"));
mStaffs.add(new Engineer("engineer-1"));
mStaffs.add(new Engineer("engineer-2"));
mStaffs.add(new Engineer("engineer-3"));
mStaffs.add(new Manager("manager-2"));
}
/**
* 为访问者展示报表
* @param visitor CEO或者CTO
*/
public void showReport(Visitor visitor) {
for (Staff staff : mStaffs) {
staff.accept(visitor);
}
}
}
如果不使用访问者模式,只用一个visit函数处理,就需要在其中对不同员工类型进行判断,然后分别处理:
class ReportUtil{
public void visit(Staff staff){
if(staff instanceof Manager){
Manager mgr = (Manager)staff;
System.out.println("经理:"+mgr.name+",KPI:"+mgr.kpi+",新产品数量"+mgr.getProducts());
}else{
Engineer eng= (Engineer)staff;
System.out.println("工程师:"+eng.name+",KPI:"+eng.kpi);
}
}
}
这就导致了if-else逻辑潜逃和强制类型转换,难易扩展和维护,当类型较多时变得复杂。而使用访问者模式,使得结构更加清晰,灵活性高。
在CEO的访问者中,CEO只关注Engineer员工的KPI,而对于Manager类型的员工除了KPI之外,还有该Manager本年度新开发产品的数量。
两类员工关注点不同,通过两个visitor方法分别进行处理。
public class CEOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
Log.v(MainActivity.TAG, "engineer:" + engineer.name + ", KPI:" + engineer.kpi);
}
@Override
public void visit(Manager manager) {
Log.v(MainActivity.TAG, "mananger:" + manager.name + ", KPI:" + manager.kpi + ",新产品数量:" + manager.getProducts());
}
}
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
Log.v(MainActivity.TAG, "engineer:"+ engineer.name + ",codeslines:" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
Log.v(MainActivity.TAG, "manager:" + manager.name + "productsNumber:" + manager.getProducts());
}
}
public class MainActivity extends AppCompatActivity {
public static String TAG = "VisitorLOG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//构建报表
BusinessReport report = new BusinessReport();
Log.v(TAG, "--------CEO visit--------------");
//设置访问者,这里是CEO
report.showReport(new CEOVisitor());
Log.v(TAG, "--------CTO visit-------------");
//注入另一个访问者, CTO
report.showReport(new CTOVisitor());
}
}
结果
V/VisitorLOG: --------CEO visit--------------
V/VisitorLOG: mananger:manager-1, KPI:0,新产品数量:5
V/VisitorLOG: engineer:engineer-1, KPI:3
V/VisitorLOG: engineer:engineer-2, KPI:6
V/VisitorLOG: engineer:engineer-3, KPI:7
V/VisitorLOG: mananger:manager-2, KPI:1,新产品数量:5
V/VisitorLOG: --------CTO visit-------------
V/VisitorLOG: manager:manager-1productsNumber:5
V/VisitorLOG: engineer:engineer-1,codeslines:62026
V/VisitorLOG: engineer:engineer-2,codeslines:34320
V/VisitorLOG: engineer:engineer-3,codeslines:53385
V/VisitorLOG: manager:manager-2productsNumber:5
五、模式的优缺点:
优点:
各角色职责分离,符合单一职责原则;
具有优秀的扩展性
使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
灵活性
缺点:
具体元素对访问者公布细节,违反了迪米特原则
具体元素变更时导致修改成本加大;
违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象。
网友评论