访问者模式的应用条件相对苛刻一些。通常它被使用在类似于这种场景:被访问类不太会增加新的子类,但是它的方法会经常变动。

先来简单回顾一些经典访问者。

传统Visitor模式

假设我们需要创建一个Encryption类,如下:

class Encryption
{
public:virtual std::string Encrypt(const std::string& strContent);
};class AESEncryption : public Encryption
{
public:virtual std::string Encrypt(const std::string& strContent) override{printf("do aes encryption\n");}
};

这个类提供加密功能。现在考虑这么一种情况,因为不同需求,经常需要更改接口函数或者新增函数,比如新增一个函数:EncryptFile().

如果接口函数变动比较频繁的话,那么访问者就很适合。我们尝试把上面的代码转变成如下的样子:

class Encryption
{
public:virtual void accept(EncryptionVisitor* v);
};class AESEncryption : public Encryption
{
public:virtual void accept(EncryptionVisitor* v) override{v->VisitAes(this);}std::string AESEncrypt(const std::string& strContent){printf("do encrypt\n");std::string strResult = strContent;return strResult;}
};class EncryptionVisitor
{
public:virtual void VisitAes(AESEncryption* e) = 0;
};class StringEncryptionVisitor : public EncryptionVisitor
{
public:StringEncryptionVisitor(const std::string& strContent) : m_strContent(strContent){}virtual void VisitAes(AESEncryption* e) override{m_strResult = e->AESEncrypt(m_strContent);}std::string GetResult() const { return m_strResult; }
private:std::string m_strContent;std::string m_strResult;
};

上面总共有4个类,Encryption,AesEncryption,EncryptionVisitor和StringEncryptionVisitor。我们现在可以这么使用:

Encryption* e = new AesEncryption();
StringEncryptionVisitor* v = new StringEncryptionVisitor("hello world");
e->accept(v);
string result = v->GetResult();

这么做有啥好处呢?比如我们现在要增加一个新的函数用来从一个本地文件读取内容,加密返回。如果不使用visitor,那么就必须在Encryption类里面增加接口函数了,从而所有Encryption的子类都要实现相应的函数。现在使用访问者,就可以这么做:

class FileEncryptionVisitor : public EncryptionVisitor
{
public:FileEncryptionVisitor(const std::string& strPath) : m_strPath(strPath){}virtual void VisitAes(AESEncryption* e) override{std::ifstream ifs(m_strPath);std::string strContent;ifs >> strContent;m_strResult = e->AESEncrypt(strContent);}std::string GetResult() const { return m_strResult; }
private:std::string m_strPath;std::string m_strResult;
};

然后调用:

Encryption* e = new AESEncryption();
FileEncryptionVisitor* v = FileEncryptionVisitor("c:\\test.txt");
e->accept(v);
std::string result = v->GetResult();

这样就成功从文件读取内容,并且加密,获取结果。

通过访问者,我们可以做到,不更改基类的接口而增加新的功能。其实,仔细看一下上面的代码,基本思路就是用一个新的访问者子类代替了原来Encryption里面的函数。每一个访问者子类都可以实现一个功能。这样如果想新增Encryption函数,只需要新增访问者子类就行了。每个访问者子类的数据成员就好像函数参数一样。

如果我们现在新增一个Encryption子类,会发生什么?

ok,这就意味这访问者类里面不得不新增一个函数,比如新增一个Encryption子类RsaEncryption,那么访问者类必须相应的增加一个函数。然后所有Visitor子类都必须实现这个函数。这是访问者模式的一个很大缺陷。

class EncryptionVisitor
{
public:virtual void VisitAes(AESEncryption* e) = 0;virtual void VisitRsa(RsaEncryption* e) = 0;
};

访问者模式的结构图如下:

访问者模式可以形象的用一个矩阵来表示:

如果现在新增一个MD5Encryption,那么EncryptionVisitor必须新增一个函数:VisitMD5(MD5Encryption* e)

同时,所有EncryptionVisitor的子类都必须实现它。

如果新增一个访问者子类,也必须实现所有访问者接口的纯虚函数。

OK, 访问者模式的一个比较大的问题其他也体现在上面的矩阵里面。每当新增一个元素子类(Encryption子类),就必须更新所有的访问者。就好象矩阵里面得填进去一样。

那么有没有办法提升呢?办法总是有的。

Uncle Bob(Robert C Martin),这哥们提出了一个ACyclic Visitor,无环访问者模式。看一下上面的结构图,Encryption和EncryptionVisit互相依赖了,这个像不像一个环。ACyclic Visitor的意图就是打破这个环。

ACyclic Visitor (无环访问者模式)

其实思路还是挺简单的,我们把访问者退化,也就是访问者里面没有任何纯虚函数。

class EncryptionVisitor
{
public:virtual ~EncryptionVisitor(){}
};

然后,访问者子类变成了

class StringEncryptionVisitor : public EncryptionVisitor
{
public:StringEncryptionVisitor(const std::string& strContent) : m_strContent(strContent){}virtual void VisitAes(AESEncryption* e){m_strResult = e->AESEncrypt(m_strContent);}std::string GetResult() const { return m_strResult; }
private:std::string m_strContent;std::string m_strResult;
};

元素类Encryption那边就变成了:

class AESEncryption : public Encryption
{
public:virtual void accept(EncryptionVisitor* v) override{StringEncryptionVisitor* sv = (StringEncryptionVisitor*)v;sv->VisitAes(this);}std::string AESEncrypt(const std::string& strContent){printf("do encrypt\n");std::string strResult = strContent;return strResult;}
};

注意在accept函数里面有一个转型动作,把父类指针转型成子类指针。这个并不是一个好的办法,通常我们是禁止这么做的。但是在特定场合也不是说不能用,就像这里。

我们可以考虑使用C++的dynamic_cast。很多时候从accept函数角度来讲,它并不知道传进来的基类指针指向哪一个子类对象。这里可以有一些技巧,比如访问者基类里面存放一个子类标记,然后accept()内部就可以根据这个子类标记来进行相对准确的转型,再调用子类函数,比如VisitAes().

这么做有什么好处呢?

考虑之前的问题,新增一个MD5Encryption类,传统访问者必须在所有子类里面实现函数VisitMD5.那么现在就无需在访问者抽象基类里面增加VisitMD5函数了。(实际上访问者抽象基类里面没有任何纯虚函数)。我们现在只需在相应的访问者子类里面来处理就是了,换句话说就是在某些对MD5Encrytion类感兴趣的访问者子类里面处理就行了
比如:

class FileEncryptionVisitor : public EncryptionVisitor
{
public:FileEncryptionVisitor(const std::string& strPath) : m_strPath(strPath){}void VisitAes(AESEncryption* e){std::ifstream ifs(m_strPath);std::string strContent;ifs >> strContent;m_strResult = e->AESEncrypt(strContent);}void VisitMD5(MD5Encrytpion* e){std::ifstream ifs(m_strPath);std::string strContent;ifs >> strContent;m_strResult = e->MD5Encrypt(strContent);}std::string GetResult() const { return m_strResult; }
private:std::string m_strPath;std::string m_strResult;
};

MD5Encryption类的accept函数如下:

void MD5Encryption::accept(EncryptionVisitor* e)
{FileEncryptionVisitor* fv = (FileEncryptionVisitor*)v;fv->VisitMD5(this);
}

客户调用还是一样:

Encryption* e = new MD5Encryption();
FileEncryptionVisitor* v = FileEncryptionVisitor("c:\\test.txt");
e->accept(v);
std::string result = v->GetResult();

ACyclic Visitor的思想就是把访问者抽象基类退化,没有任何接口需要子类实现。每个访问者子类都自己负责被元素类调用的函数。然后元素类(被访问者)通过一个基类到子类的转型来调用访问者具体类的函数。这么做的好处就是访问者抽象类跟被访问类之间解耦了。使得新增被访问类的代价减少了。无环访问者的结构图如下:

跟传统访问者相比较,

1. EncryptionVisitor不再依赖于元素具体类。取而代之的是访问者子类依赖于元素具体类。

2. 每一个访问者具体类并不一定要依赖于所有的元素具体类,可以只依赖于需要的元素具体类。

之前说到传统访问者就像是一个矩阵,那么无环访问者呢?就像是一个稀疏矩阵。

有关访问者,个人理解现在也就这么多了。访问者的使用条件相对比较苛刻一些,但是在某些场合也确实很有前途。

在使用的时候是选择传统的,还是无环访问者,就视具体情况而定了。如果元素类(被访问者)变动相对多一些,那么无环访问者模式就相对合适一些。

当然,任何面向对象的模式都是会带来一定的副作用的,这点需要牢记在心,不要滥用。

再议访问者模式 - Visitor vs Acyclic Visitor相关推荐

  1. Design Pattern: Visitor and Acyclic Visitor and their differences

    Design Pattern 笔记 Visitor and Acyclic Visitor 小记 extensibility, behavioral 前言 在Design Pattern上面学习设计模 ...

  2. 访问者模式 php,php设计模式 Visitor 访问者模式

    /** * 访问者模式 * * 表示一个作用于某对象结构中的各元素的操作,可以在不改变各元素的类的前提下定义作用于这些元素的新操作 * */ abstract class Visitor { abst ...

  3. 设计模式 -- 访问者模式(Visitor Pattern)

    封装一些作用于某些数据结构中的各元素的操作,它可以在不改变数据结构的前提下赋予这些元素新的操作. 应用场景 对象结构比较稳定,但是需要在对象结构的基础上定义新的操作. 需要对同一个类的不同对象执行不不 ...

  4. 设计模式 -- 访问者模式(Visitor)

    写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------- 主要内容包括: 初识访问 ...

  5. 设计模式——访问者模式

    访问者模式 访问者模式(Visitor Pattern):封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作. 访问者模式就是根据朋友的信息,执行了 ...

  6. 设计模式之访问者模式

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 本次LZ和各位分享一下访问者 ...

  7. 二十二:访问者模式(伪动态双分配)

    定义(源于GoF<Design Pattern>):表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作. 初次接触,定义会显得晦涩并且难 ...

  8. (二十二)访问者模式详解(伪动态双分派) - 转

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 本次LZ和各位分享一下访问者模式,从场景.设计初衷以及实现方面来说,访问者模式算是LZ即将写到的24种设计模式当中,最复杂也是最难理解 ...

  9. 小白设计模式:访问者模式

    定义 可作用于对象结构中各个元素,在不改变各元素类的前提下,定义作用于这些元素新操作方法的一种行为型设计模式. 主要组成 抽象访问者(Visitor): 声明出对对象结构中每一个具体元素的访问方法vi ...

最新文章

  1. 软件测试可分为哪几种
  2. InsightFace笔记
  3. 兔子繁殖MATLAB,2011-2012数学建模题
  4. 服务器损坏文件怎么办,服务器文件系统损坏的处理
  5. github怎么隐藏自己的pr记录_记便签的软件哪个好?怎么及时记录自己的想法
  6. React开发(249):react项目理解 ant design input loading
  7. python画图模块_学习python画图模块plotnine:第一步安装
  8. vue组件内数值做watch监听,首次监听不到的问题
  9. 服务器端文件名,挑战服务端各文件名中文含义及详细说明
  10. linux 会话 进程组 守护进程
  11. 什么是C++标准库?
  12. 西门子200PLC指令详解——比较指令
  13. 《机器人学导论》-《计算多体动力学》两本教材角速度传递的理解
  14. VMware Funsion 8.5.1破解版
  15. no-cache和no-store的区别
  16. npm install 安装一直报错Error EPERM operation not permitted, mkdir
  17. Integer 十六进制
  18. week11作业——C - 必做题11-3
  19. GTK构件 --- 文本视图控件GTKtextview
  20. 基础连接已关闭解决办法_手机wifi连不上怎么办 手机wifi连不上解决办法【详解】...

热门文章

  1. Android 6.0 Marshmallow介绍
  2. 软件测试 | 测试开发 | Spring boot 之 RestTemplate访问
  3. 在php中调用接口以及编写接口
  4. Unity打开电脑本地文件夹选择图片替换
  5. 数据库关系代数思维导图
  6. Ehcache基本使用
  7. 微信公众平台开发测试账号配置URL失败的问题
  8. mysql 拼音查询_mysql中文字段转拼音首字母,以及中文拼音模糊查询
  9. 离散数学(二):命题公式的等值演算
  10. yolov5的anchor详解