Visitor 访问器也是属于“行为变化”模式。
文章目录
- 1. 动机( Motivation)
- 2. 代码演示Visitor 访问器
- 3. 模式定义
- 4. 结构(Structure)
- 5. 要点总结
- 6. 其他参考
1. 动机( Motivation)
- 在软件构建过程中,由于需求的改变,某些
类层次结构
中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
比如以下为类层次结构:Element为基类,ElementA 、ElementB是子类
#include <iostream>
using namespace std;class Visitor;class Element
{
public:virtual void Func1() = 0;virtual ~Element(){}
};class ElementA : public Element
{
public:void Func1() override{//...} };class ElementB : public Element
{
public:void Func1() override{//***}};
Func1()
为已经有的行为,假如需求有变更,需要添加新的行为,大家直观的操作肯定是直接在基类中加新的行为例如Func2()
,在子类中override对应的行为。
#include <iostream>
using namespace std;class Visitor;class Element
{
public:virtual void Func1() = 0;virtual void Func2(int data)=0;virtual void Func3(int data)=0;//...virtual ~Element(){}
};class ElementA : public Element
{
public:void Func1() override{//...}void Func2(int data) override{//...}};class ElementB : public Element
{
public:void Func1() override{//***}void Func2(int data) override {//***}};
上面的操作是很常见的操作,但这不一个类的设计过程,我们是讲当你已经完成了代码,已经部署分发了又想添加新的操作,这样改来代价就是很高的,因为你改的是基类,我们有一个前提说你要在一个基类里去添加针对这个对象结构的所有的行为,每一个子类都要重写的版本,上面的这种写法是违背了设计原则的:开闭原则。遇到这种情况怎么办呢?
- 如何在不更改类层次结构的前提下,在
运行时
根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
2. 代码演示Visitor 访问器
为了解决上面存在的问题,我们来看Vistor模式是如何解决的呢?
整体代码:
#include <iostream>
using namespace std;class Visitor;class Element
{
public:virtual void accept(Visitor& visitor) = 0; //第一次多态辨析virtual ~Element(){}
};class ElementA : public Element
{
public:void accept(Visitor &visitor) override {visitor.visitElementA(*this);}};class ElementB : public Element
{
public:void accept(Visitor &visitor) override {visitor.visitElementB(*this); //第二次多态辨析}};class Visitor{
public:virtual void visitElementA(ElementA& element) = 0;virtual void visitElementB(ElementB& element) = 0;virtual ~Visitor(){}
};//==================================//扩展1
class Visitor1 : public Visitor{
public:void visitElementA(ElementA& element) override{cout << "Visitor1 is processing ElementA" << endl;}void visitElementB(ElementB& element) override{cout << "Visitor1 is processing ElementB" << endl;}
};//扩展2
class Visitor2 : public Visitor{
public:void visitElementA(ElementA& element) override{cout << "Visitor2 is processing ElementA" << endl;}void visitElementB(ElementB& element) override{cout << "Visitor2 is processing ElementB" << endl;}
};int main()
{Visitor2 visitor;ElementB elementB;elementB.accept(visitor);// double dispatchElementA elementA;elementA.accept(visitor);return 0;
}
代码分析:
Vistor模式有一个前提就是我能预料到未来可能会给整个类层次结构增加新的操作,但是不知道要加什么和多少的操作,这个时候就需要进行预先的设计
class Element
{
public:virtual void accept(Visitor& visitor) = 0; //第一次多态辨析virtual ~Element(){}
};
virtual void accept(Visitor& visitor) = 0; //第一次多态辨析
函数,参数为Visitor类对象,在Visitor类中针对每一个子类写一个虚函数
class Visitor{
public:virtual void visitElementA(ElementA& element) = 0;virtual void visitElementB(ElementB& element) = 0;virtual ~Visitor(){}
};
ElementA继承自Element,然后重写accept()函数,接受Visitor作为参数,调用visitElementA(),传入的为this,也就是ElementA。这样的写法只是预先的设计了将来可能会增加新的操作
class ElementA : public Element
{
public:void accept(Visitor &visitor) override {visitor.visitElementA(*this);}};
ElementB也是一样的操作
在整个代码中//==================================
之上是已经做好的设计,之下就是将来,现在我又新的需求了,我需要给前面整个类层次结构增加新的操作,怎么做呢?
class Visitor1继承Visitor,在子类中实现visitElementA和visitElementB的虚函数,
//扩展1
class Visitor1 : public Visitor{
public:void visitElementA(ElementA& element) override{cout << "Visitor1 is processing ElementA" << endl;}void visitElementB(ElementB& element) override{cout << "Visitor1 is processing ElementB" << endl;}
};
将来有新的需求,就增加Visitor2,去继承Visitor。
//扩展2
class Visitor2 : public Visitor{
public:void visitElementA(ElementA& element) override{cout << "Visitor2 is processing ElementA" << endl;}void visitElementB(ElementB& element) override{cout << "Visitor2 is processing ElementB" << endl;}
};
假如说我需要添加Visitor2的操作,Visitor2 visitor;
先创建Visitor2的对象,然后ElementB elementB;
创建ElementB对象,需要给ElementB添加操作elementB.accept(visitor);
,这个调用是Visitor 模式中最重要的需要理解了。
首先elementB.accept(visitor);
找class ElementB : public Element
去调用,visitor.visitElementB(*this);
,就会找到class Visitor2 : public Visitor
中的void visitElementB(ElementB& element) override
,那么执行的命令也就是:cout << "Visitor2 is processing ElementB" << endl;
,也就是我们想给ElementB添加Visitor2操作
int main()
{Visitor2 visitor;ElementB elementB;elementB.accept(visitor);// double dispatch//给ElementA添加Visitor2ElementA elementA;elementA.accept(visitor);return 0;
}
所以Visitor模式中有一个double dispatch(二次多态辨析),哪两次呢?
第一次是调用accept()虚函数时候;第二次是visitor.visitElementB
3. 模式定义
表示一个作用于某对象结构(Element)中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。
----《设计模式》GoF
class Visitor1 : public Visitor
和class Visitor2 : public Visitor
都是新的操作,每一次添加一次新操作都是扩展,这就是开闭原则的体现–利用子类的方式实现新的操作。
4. 结构(Structure)
上图是《设计模式》GoF中定义的Visitor 访问器的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
这个设计模式很多人会有感受就是它有一些缺点,这个模式不是随便就能用的。上图中Vistor
和Element
是需要稳定的,但是这两个稳定是不够的。因为Vistor稳定,里面需要ConcreteElementA和ConcreteElementB,实际上也就需要ConcreteElementA和ConcreteElementB都必须是稳定。因为当你写Vistor的时候,必须知道Element有几个子类,所以Vistor写下来的前提是Element类的子类个数能确定,这是一个非常大的前提,很容易保证不到。
假如我要设计一个图像系统,其中有shape基类,rectangle子类,circle子类等,但Vistor要求这些子类必须稳定,这个条件常常满足不了,如果没满足,Element中新加一个子类,假如是ElementC,那么Vistor基类也需要跟着改变,又打破了开闭原则
所以Vistor基类的稳定的前提是Element所有的子类全确定,这是这个模式非常大的缺点,我们也需要说一下,蓝色的部分是可以扩展式的变化,将来需要添加新的操作的时候,就去继承Vistor实现Vistor新的子类,比如说Vistor3,针对它里面所有的Element去写所有的函数。
坦白来说,这个模式要求的稳定就带来了极大的脆弱性,如果Element类层次结构不能稳定,Vistor就不能使用了,这个条件十分苛刻
5. 要点总结
- Visitor模式通过所谓双重分发(double dispatch)来实现在不更改(不添加新的操作-编译时时)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。
不在Element上添加新的源代码;//==================================
以下为运行时…
-
所谓双重分发即Visitor模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visitElementX方法的多态辨析。
-
Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类)会导致Vistor类的改变。因此Vistor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。
6. 其他参考
C++设计模式——访问者模式