C++实现Visitor访问者模式 & 头文件循环包含的问题

桥接模式

翻译:https://springframework.guru/gang-of-four-design-patterns/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.”

表示要在对象结构的元素上进行的操作。Visitor模式允许你在不修改被操作对象的前提下给被操作对象增加新的操作。

适用:

  将数据与行为分开,这样后续增加数据对象、或增加行为都很简单,属于行为模式中的一种,注意与Bridge桥接模式进行对比。

 类图:

 

 

 

  •  Element:接口,定义一个以Visitor为入参的accept()方法
  • ConcreteElement:实现Element类定义的accept()方法
  • Visitor:接口,声明visit()函数,该函数用于对象结构中的每个ConcreteElement,即:有多少个ConcreteElement,就有多少个visit()函数
  • ConcreteVisitor:实现Visitor声明的函数

例子

  下面是一个邮件客户端配置应用,mail client configurator application。首先定义Element类:

#ifndef _MAIL_CLIENT_H_
#define _MAIL_CLIENT_H_

#include <string>
#include <vector>
using std::string;
using std::vector;

//#include "MailClientVisitor.h"  //注意:使用的是前置声明,而不是#include
class MailClientVisitor;

class MailClient
{
public:
    MailClient(){}
    virtual ~MailClient(){}        //用于多态的父类,析构函数最好是virtual;

    virtual void sendMail(vector<string> mailInfo) = 0;
    virtual void receiveMail(vector<string> mailInfo) = 0;
    virtual bool accept(MailClientVisitor* visitor) = 0;
};

#endif // !_MAIL_CLIENT_H

  上面的MailClient定义了收发邮件相关的函数,并定义了accept()函数,该函数以一个Visitor对象为入参。下面定义MailClient的三个子类:

OperaMailClient:

#ifndef _OPERA_MAIL_CLIENT_H_
#define _OPERA_MAIL_CLIENT_H_

#include "MailClient.h"

class OperaMailClient : public MailClient
{
public:
    OperaMailClient();
    ~OperaMailClient();

    virtual void sendMail(vector<string> mailInfo) override;
    virtual void receiveMail(vector<string> mailInfo) override;
    virtual bool accept(MailClientVisitor *visitor) override;
};

#endif // !_OPERA_MAIL_CLIENT_H_
#include "OperaMailClient.h"
#include <iostream>

#include "MailClientVisitor.h"        //!!!!!!!在父类的头文件中,使用的是前置声明,
                                    //前置声明中没有函数定义,因此在.cpp中需要包含头文件,以调用函数
using namespace std;

OperaMailClient::OperaMailClient()
{
}

OperaMailClient::~OperaMailClient()
{
}

void OperaMailClient::sendMail(vector<string> mailInfo)
{
    cout << "OperaMailClient:Sending mail" << endl;
}

void OperaMailClient::receiveMail(vector<string> mailInfo)
{
    cout << "OperaMailClient:Receiving mail" << endl;
}

bool OperaMailClient::accept(MailClientVisitor* visitor)
{
    visitor->visit(this);
    return true;
}

  另外两个:SquirrelMailClient ZimbraMailClient与之类似,省略。

  在上面的代码中,重点看子类实现的accept(Visitor)函数,在该函数中入参为一个Visitor,而在函数中调用Visitor的visit()函数时,我们又将Element本身作为参数传递给Visitor。对于具体的Element类,在运行时Element函数的accept调用Visitor函数,而Visitor类会对具体的Element类调用visit()函数,这称为double dispatch。但是Java、C++并不支持double dispatch。Visitor模式可以让我们模拟double dispatch。

  对于每个Element类,我们把对它的相关操作封装在Visitor子类的visit()函数中,即:将相关的操作从Element类中移出。这样做的好处在于:实现了操作与数据本身的解耦,以后添加一个操作,或添加一个Element都只改变其相关代码即可。

  Visitor的接口定义如下:

#ifndef _MAIL_CLIENT_VISITOR_H_
#define _MAIL_CLIENT_VISITOR_H_

#include "OperaMailClient.h"
#include "SquirrelMailClient.h"
#include "ZimbraMailClient.h"

//class OperaMailClient;
//class SquirrelMailClient;
//class ZimbraMailClient;

class MailClientVisitor
{
public:
    MailClientVisitor(){}
    virtual ~MailClientVisitor(){}

    virtual void visit(OperaMailClient* mailClient) = 0;
    virtual void visit(SquirrelMailClient* mailClient) = 0;
    virtual void visit(ZimbraMailClient* mailClient) = 0;
};
#endif // !_MAIL_CLIENT_VISITOR_H_

  其子类LinuxMailClientVisitor实现如下:

#ifndef _LINUX_MAIL_CLIENT_VISITOR_H_
#define _LINUX_MAIL_CLIENT_VISITOR_H_

#include "MailClientVisitor.h"

class LinuxMailClientVisitor : public MailClientVisitor
{
public:
    LinuxMailClientVisitor();
    ~LinuxMailClientVisitor();

    virtual void visit(OperaMailClient* mailClient) override;
    virtual void visit(SquirrelMailClient* mailClient) override;
    virtual void visit(ZimbraMailClient* mailClient) override;
};

#endif // !_LINUX_MAIL_CLIENT_VISITOR_H_
#include "LinuxMailClientVisitor.h"
#include <iostream>
using std::cout;
using std::endl;


LinuxMailClientVisitor::LinuxMailClientVisitor()
{
}


LinuxMailClientVisitor::~LinuxMailClientVisitor()
{
}

void LinuxMailClientVisitor:: visit(OperaMailClient* mailClient) 
{
    cout << "Configuration of Opera mail client for Linux complete" << endl;
}

void LinuxMailClientVisitor:: visit(SquirrelMailClient* mailClient) 
{
    cout << "Configuration of Squirrel mail client for Linux complete" << endl;
}

void LinuxMailClientVisitor:: visit(ZimbraMailClient* mailClient) 
{
    cout << "Configuration of Zimbra mail client for Linux complete" << endl;
}

  在具体的Visitor中,实现对数据相关的操作,如上面的visit()方法。在上述代码中,不同的Visitor子类的可以访问同一个具体的Element,如OperaMailClient类;而不同的Element类可以被同一个Visitor类访问。因此,一个具体的操作依赖于visitor以及具体的Element类(double dispatch)。

测试代码:

#include <iostream>

#include "MailClient.h"
#include "OperaMailClient.h"
#include "SquirrelMailClient.h"
#include "ZimbraMailClient.h"

#include "MailClientVisitor.h"
#include "LinuxMailClientVisitor.h"
#include "MacMailClientVisitor.h"
#include "WindowsMailClientVisitor.h"

int main(int argc, char *argv[])
{
    MailClientVisitor* macVisitor = new MacMailClientVisitor();
    MailClientVisitor* linuxVisitor = new LinuxMailClientVisitor();
    MailClientVisitor* winVisitor = new WindowsMailClientVisitor();

    MailClient* operaMailClient = new OperaMailClient();
    MailClient* squirrelMailClient = new SquirrelMailClient();
    MailClient* zimbraMailClient = new ZimbraMailClient();

    //===test opera mail client
    operaMailClient->accept(macVisitor);
    operaMailClient->accept(linuxVisitor);
    operaMailClient->accept(winVisitor);

cout << "======================" << endl;
squirrelMailClient->accept(macVisitor);
squirrelMailClient->accept(linuxVisitor);
squirrelMailClient->accept(winVisitor);


cout << "======================" << endl;
zimbraMailClient->accept(macVisitor);
zimbraMailClient->accept(linuxVisitor);
zimbraMailClient->accept(linuxVisitor);

    system("pause");
}

输出:

总结: 

  Visitor模式是最为复杂的模式之一。一个Visitor可以访问不同对象结构,例如使用组合模式的对象组合、或一个继承树。在合适的情景下使用Visitor模式可以简化代码结构,并容易扩展。

访问者模式与桥接模式

  对比,两者长的很像。。。

桥接模式类图:

 

 

  参考:https://blog.csdn.net/larry_lv/article/details/7053776

  •  桥接模式(属于:Structual Design Pattern):将抽象部分(属性)与它的实现部分分离,使得它们可以独立地变化
  • 访问者模式(属于:Behavior Design Pattern):表示一个作用于某对象结构中的各元素的操作;可以在不改变各元素类的前提下定义作用于这些元素的新操作

 

头文件循环包含问题

参考:https://blog.csdn.net/centurionAX/article/details/107984778

情景:

  在类A需要调用类B的方法,而在类B中也需要调用类A的方法,在类A的头文件中会#include B,而在类B的头文件中会#include A,从而引起头文件的循环包含。在解析代码时,先将A.h中的内容展开;A.h的开头包含了B.h,再将B.hA.h的开头展开;B.h的开头虽然又包含了A.h,但是由于此时A.h已经被包含过一次,在#pragma once指令或者头文件宏的约束下会避免将Parent.h再次展开,因此编译时会提示XXX未定义的错误。

解决方法:

  在A B互相包含时,在头文件中使用类的前置声明,而不是头文件;在.cpp文件中使用#include包含需要调用方法的类的头文件,这样就不会产生编译错误。

posted @ 2020-09-18 14:45  adfas  阅读(425)  评论(0编辑  收藏  举报