表示一个作用于某对象结构中的各元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
“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.” – GoF
类层次结构中可能经常由于引入新的操作,从而将类型变得很脆弱。比如假定三个类Rectangle、Triangle和Line继承自抽象类Shape,现在由于某种原因需要在抽象类Shape中增加一个abstract (或者pure virtual)方法,如果使用通常的方法,那么它所有的子类,即Rectangle、Triangle和Line都需要修改。
由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有的设计。Visitor设计模式就是在不更改类层次结构的前提下,在运行时根据需要动态地为类层次结构上的各个类动态添加新的操作(方法)。
下面是Visitor模式的UML类图:
Visitor设计模式要解决的问题:
- 类层次结构不变,但类的方法需要增加;
- 预料到基类有更改,但不知道是何种具体更改。
要增加一个方法,实际上就是增加一个Visitor的子类。
业务实例:
有Rectangle和Circle两种形状,在不改动他们代码的情况下,分别为它们增加填充(Fill)和附加文字(AddText)的功能。
实现上面业务实例的C++代码:
// Visitor.h
#include <iostream>
using namespace std;
class Visitor;
class Shape
{
public:
// draw为已经确知需要的方法
virtual void draw() = 0;
// 1. 预料将来可能会引入新的方法,因此在这里预留一个accept方法
// 2. 可以通过下面的accept方法,为Shape的子类增加一个或多个方法
virtual void accept(Visitor *visitor) = 0;
public:
virtual ~Shape()
{
cout << "in the destructor of Shape..." << endl;
}
};
class Rectangle : public Shape
{
public:
void draw();
void accept(Visitor *visitor);
public:
~Rectangle()
{
cout << "in the destructor of Rectangle..." << endl;
}
};
class Circle : public Shape
{
public:
void draw();
void accept(Visitor *visitor);
public:
~Circle()
{
cout << "in the destructor of Circle..." << endl;
}
};
class Visitor
{
public:
// 注意此处参数为具体类,而非抽象类Shape。
// Shape有多少个子类,就需要在此处重载多少次visit方法。
// 除非使用RTTI,则可以只有一个visit方法,并且该方法的参数类型为抽象的Shape*,同时需要在visit方法中确定,
// 传入的参数到底是Shape的哪个具体的子类。
virtual void visit(Rectangle *shape) = 0;
virtual void visit(Circle *shape) = 0;
public:
virtual ~Visitor()
{
cout << "in the destructor of Visitor..." << endl;
}
};
// 假定要为Shape的子类增加一个填充(Fill)操作,那么就为Visitor增加一个子类,即FillVisitor
class FillVisitor : public Visitor
{
public:
void visit(Rectangle *shape)
{
// 为类Rectangle增加一个方法,由于实现这个新方法可能需要用到原来对象中的属性或者方法,因此需要
// 将自身(this)传到这里。理论上而言,如果不用到原来对象的原有的属性或者方法,那么visit方法是可以
// 没有参数的;另外一种情况就是,如果必要,visit方法也可以拥有多个参数
cout << "Here is the newly added Fill() method for class Rectangle." << endl;
}
void visit(Circle *shape)
{
// 为类Circle增加一个方法,由于实现这个新方法可能需要用到原来对象中的属性或者方法,因此需要
// 将自身(this)传到这里。理论上而言,如果不用到原来对象的原有的属性或者方法,那么visit方法是可以
// 没有参数的;另外一种情况就是,如果必要,visit方法也可以拥有多个参数
cout << "Here is the newly added Fill() method for class Circle." << endl;
}
public:
~FillVisitor()
{
cout << "in the destructor of FillVisitor..." << endl;
}
};
// 假定要为Shape的子类增加一个附加文字(AddText)操作,那么就为Visitor增加一个子类,即AddTextVisitor
class AddTextVisitor : public Visitor
{
public:
void visit(Rectangle *shape)
{
// 为类Rectangle增加一个方法,由于实现这个新方法可能需要用到原来对象中的属性或者方法,因此需要
// 将自身(this)传到这里。理论上而言,如果不用到原来对象的原有的属性或者方法,那么visit方法是可以
// 没有参数的;另外一种情况就是,如果必要,visit方法也可以拥有多个参数
cout << "Here is the newly added AddText() method for class Rectangle." << endl;
}
void visit(Circle *shape)
{
// 为类Circle增加一个方法,由于实现这个新方法可能需要用到原来对象中的属性或者方法,因此需要
// 将自身(this)传到这里。理论上而言,如果不用到原来对象的原有的属性或者方法,那么visit方法是可以
// 没有参数的;另外一种情况就是,如果必要,visit方法也可以拥有多个参数
cout << "Here is the newly added AddText() method for class Circle." << endl;
}
public:
~AddTextVisitor()
{
cout << "in the destructor of AddTextVisitor..." << endl;
}
};
// Visitor.cpp
#include "Visitor.h"
void Rectangle::draw()
{
cout << "Implement method Rectangle::draw() here!" << endl;
}
void Rectangle::accept(Visitor *visitor)
{
visitor->visit(this); // 转发到visitor的visit方法,并将this作为参数传递过去
}
void Circle::draw()
{
cout << "Implement method Circle::draw() here!" << endl;
}
void Circle::accept(Visitor *visitor)
{
visitor->visit(this); // 转发到visitor的visit方法,并将this作为参数传递过去
}
// PatternClient.cpp
#include "Visitor.h"
int main(int argc, char **argv)
{
Visitor *visitor1 = new FillVisitor();
Shape *rectangle = new Rectangle();
// 调用该形状已经实现的方法
rectangle->draw();
// 通过visitor1调用新增加的方法
rectangle->accept(visitor1);
Shape *circle = new Circle();
// 调用该形状已经实现的方法
rectangle->draw();
// 通过visitor1调用新增加的方法
circle->accept(visitor1);
cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;
Visitor *visitor2 = new AddTextVisitor();
// 通过visitor2调用新增加的方法
rectangle->accept(visitor2);
// 通过visitor2调用新增加的方法
circle->accept(visitor2);
cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;
delete visitor1;
delete visitor2;
delete rectangle;
delete circle;
return 0;
}
运行结果:
Implement method Rectangle::draw() here!
Here is the newly added Fill() method for class Rectangle.
Implement method Rectangle::draw() here!
Here is the newly added Fill() method for class Circle.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is the newly added AddText() method for class Rectangle.
Here is the newly added AddText() method for class Circle.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
in the destructor of FillVisitor...
in the destructor of Visitor...
in the destructor of AddTextVisitor...
in the destructor of Visitor...
in the destructor of Rectangle...
in the destructor of Shape...
in the destructor of Circle...
in the destructor of Shape...
结果符合预期。
由上面的代码可以很清楚地知道,我们如果要为Rectangle或Circle增加新方法,只需要多增加一个Visitor的子类即可,而Rectangle或Circle这两个类本身的代码,没无任何改动。
上面代码对应的UML类图:
双向分发(Double Dispatch)
- accept(Visitor *visitor)方法在Shape的子类 (如Rectangle)中;
- visit(Rectangle *rectangle)等visit方法在Visitor的子类 (如FillVisitor)中;
- 两处重要的多态特性:
n 调用accept方法的对象之多态辨析,即是谁调用accept方法?(在本例中,是Rectangle还是Circle?)
n accept方法的参数之多态辨析,即accept方法的参数到底是那个Visitor的子类
正是这两处多态特性的使用,就形成了所谓的双向分发(Double Dispatch)机制,即首先将visitor作为参数传递给Shape的子类(在本例中即Rectangle和Circle);然后在Shape子类的accept方法中调用visitor的visit方法,而且visit方法的参数是this,这样Shape子类就将自己传递到了visitor类的visit方法中,然后在visitor的visit方法中为Shape的子类增加需要的新的行为。这就是双向分发的核心思想,即visitor被传递(分发)了一次,即visitor被传递到Shape子类中一次,Shape的子类也被传递(分发)了一次,即Shape的子类被传递到visitor中一次。
从更高层次来看这个问题:Shape中的accept()以Visitor作为参数,Visitor中的visit以Shape作为参数。
上面给出代码的调用顺序: