1、定义
1.1 标准定义
访问者模式(Visitor Pattern)是一个相对简单的模式,其定义如下:Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.(封装一些作用于某种数据结构中的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)
1.2 类图
● Visitor——抽象访问者
抽象类或者接口, 声明访问者可以访问哪些元素, 具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。
● ConcreteVisitor——具体访问者
它影响访问者访问到一个类后该怎么干, 要做什么事情。
● Element——抽象元素
接口或者抽象类, 声明接受哪一类访问者访问, 程序上是通过accept方法中的参数来定义的。
● ConcreteElement——具体元素
实现accept方法, 通常是visitor.visit(this), 基本上都形成了一种模式了。
● ObjectStruture——结构对象
元素产生者, 一般容纳在多个不同类、 不同接口的容器, 如List、 Set、 Map等, 在项目中, 一般很少抽象出这个角色。
2、实现
2.1 类图
抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。
具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
抽象节点(Element)角色:声明一个接受操作,接受一个访问者对象作为一个参量。
具体节点(ConcreteElement)角色:实现了抽象元素所规定的接受操作。
结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。
2.2 代码
#include <iostream> #include <string> #include <list> using namespace std; class Element; class Visitor { public: virtual void Visit( Element *element ){}; }; // "Element" class Element { public: // Methods virtual void Accept( Visitor *visitor ){}; }; // "ConcreteElement" class Employee : public Element { public: string name; double income; int vacationDays; public : Employee( string name, double income, int vacationDays ) { this->name = name; this->income = income; this->vacationDays = vacationDays; } void Accept( Visitor *visitor ) { visitor->Visit( this ); } }; class IncomeVisitor : public Visitor { public: void Visit( Element *element ) { Employee *employee = ((Employee*)element); employee->income *= 1.10; cout<<employee->name<<" 's new income: " <<employee->income<<endl; } }; class VacationVisitor : public Visitor { public : void Visit( Element *element ) { Employee *employee = ((Employee*)element); // Provide 3 extra vacation days employee->vacationDays += 3; cout<<employee->name<<" 's new vacation days: " <<employee->income<<endl; } }; // "ObjectStructure" class Employees { private : list< Employee*> employees; public : void Attach( Employee *employee ) { employees.push_back(employee); } void Detach( Employee *employee ) { employees.remove(employee); } void Accept( Visitor *visitor ) { for (std::list<Employee*>::iterator it=employees.begin(); it != employees.end(); ++it) (*it)->Accept(visitor); } }; void main( ) { Employees *e = new Employees(); e->Attach( new Employee( "Tom", 25000.0, 14 ) ); e->Attach( new Employee( "Thomas", 35000.0, 16 ) ); e->Attach( new Employee( "Roy", 45000.0, 21 ) ); // Create two visitors IncomeVisitor *v1 = new IncomeVisitor(); VacationVisitor *v2 = new VacationVisitor(); // Employees are visited e->Accept( v1 ); e->Accept( v2 ); }
3、总结
3.1 优点
● 符合单一职责原则
具体元素角色也就是Employee抽象类的两个子类负责数据的加载, 而Visitor类则负责报表的展现, 两个不同的职责非常明确地分离开来, 各自演绎变化。
● 优秀的扩展性
由于职责分开, 继续增加对数据的操作是非常快捷的。
● 灵活性非常高
如果有EmployeeA、EmployeeB、EmployeeC三个甚至更多的元素需要处理,每个元素处理不同,如果你用迭代器加类型判断,OK!可以完成功能,但可能效率很低,多个判断显得代码很乱,如果你用访问者模式,重载某个函数,参数分别传入EmployeeA、EmployeeB、EmployeeC三个类,是不是可以一步解决呢,这就是访问者模式的优势所在,所以,在某些处理方式上面,访问者模式的灵活性相当高。
3.2 缺点
● 具体元素对访问者公布细节
访问者要访问一个类就必然要求这个类公布一些方法和数据, 也就是说访问者关注了其他类的内部细节, 这是迪米特法则所不建议的。
● 具体元素变更比较困难
具体元素角色的增加、 删除、 修改都是比较困难的, 就上面那个例子, 你想想, 你要是想增加一个成员变量, 如年龄age, Visitor就需要修改, 如果Visitor是一个还好办, 多个呢?业务逻辑再复杂点呢?
● 违背了依赖倒置转原则
访问者依赖的是具体元素, 而不是抽象元素, 这破坏了依赖倒置原则, 特别是在面向对象的编程中, 抛弃了对接口的依赖, 而直接依赖实现类, 扩展比较难。
3.3 使用场景
● 一个对象结构包含很多类对象, 它们有不同的接口, 而你想对这些对象实施一些依赖于其具体类的操作, 也就说是用迭代器模式已经不能胜任的情景。
● 需要对一个对象结构中的对象进行很多不同并且不相关的操作, 而你想避免让这些操作“污染”这些对象的类。
总结一下, 在这种地方你一定要考虑使用访问者模式: 业务规则要求遍历多个不同的对象。 这本身也是访问者模式出发点, 迭代器模式只能访问同类或同接口的数据( 当然了, 如果你使用instanceof, 那么能访问所有的数据, 这没有争论) , 而访问者模式是对迭代器模式的扩充, 可以遍历不同的对象, 然后执行不同的操作, 也就是针对访问的对象不同, 执行不同的操作。 访问者模式还有一个用途, 就是充当拦截器( Interceptor) 角色。