第25章 行为型模式—访问者模式

1. 访问者模式(Visitor Pattern)的定义

(1)定义:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作

  ①模式是要解决当为对象添加新的操作和功能时候,如何尽可能不修改对象的类的一种方法。一般为对象添加功能,是需要向对象添加成员函数,但这里是通过添加一个访问者,由访问者去实现对象需要的新功能。

  ②该模式主要是针对为一系列对象添加不同功能的。当然如果是简化版的话,也可以为一个对象添加新功能。

(2)访问者模式的结构和说明

第25章 行为型模式—访问者模式 

  ①Visitor:访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能。

  ②ConcreteVisitor:具体的访问者实现对象,实现要真正被添加到对象结构中的功能。(如为元素A添加A功能,元素B添加B功能)。

  ③Element:抽象的元素对象,对象结构的顶层接口,定义接受访问的操作

  ④ConcreteElement:具体元素对象,对象结构中的具体对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用

  ⑤ObjectStructure:对象集合或复合,通常包含多个被访问对象,它可以遍历多个被访问的对象,也可以让访问者访问它的元素。

(3)思考访问者模式:本质——预留通路,回调实现

  ①访问者模式能给一系列对象透明的添加新的功能,从而避免在维护期间对这一系列对象进行修改,而且还能变相实现复用访问者所具有的功能。

  ②访问者模式主要是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者对象上定义visit方法

  ③然后在调用真正发生的时候,通过两次分发技术,利用预先定义好的通路,回调到访问者具体的实现上。

【编程实验】扩展客户管理的功能

第25章 行为型模式—访问者模式

//行为型模式——访问者模式
//场景:扩展客户管理的功能
/*
说明:
1. 公司客户分为企业客户和个人客户
2. 目前的功能:客户提出服务申请
3. 需要扩展的功能:
  (1)客户对公司产品的偏好分析。针对企业和个人客户有不同的分析策略。主要是根据以往购买的历史、潜在购买意向等分析。对于企业客户还要分析其所在行
业的发展趋势、客户的发展预期等 (2)客户价值分析。针对企业和个人客户,有不同的分析策略。主要根据购买的金额大小、购买的产品和服务的多少、购买的频率进行分析的。 4. 潜在功能:不同客户的需求调查、满意度分析、客户消费预期分析等。 解决方案: 1. 对象结构就两种:企业客户和个人客户,如何在不想改变类,又要添加新的功能。 2. 定义一个接口代表要新加入的功能。 3. 在客户上添加一个通用的accept方法传入代表新加入的功能对象。 4. 实现新功能对象。 5. 循环访问整个对象结构的类,让这个类来提供符合客户端业务需求的方法。
*/ #include <iostream> #include <string> #include <list> using namespace std; class EnterpriseCustomer; //前置声明 class PersonalCustomer; //前置声明 //*****************************访问者的接口(Visitor角色)************************** class Visitor { public: //访问企业客户,相当于给企业客户添加访问者的功能 virtual void visitEnterpriseCustomer(EnterpriseCustomer& ec) = 0; //访问个人客户对象,相当于给个人客户添加访问者的功能 virtual void visitPersonalCustomer(PersonalCustomer& pc) = 0; }; //**********************************被访问对象(Elment角色))********************* //被访问对象,客户接口 class Customer { private: string customerId; string name; public: string& getCustomerId(){return customerId;} void setCustomerId(string value) { this->customerId = value; } string& getName(){return this->name;} void setName(string value) { this->name = value; } //接受访问者的访问(注意,这里不直接实现,而只是 //提供一个接口原因是,在方法内部会调用visitor的不同的 //方法,并且传入一个this指针,而这个指针指的是具体的不同客户) virtual void accept(Visitor* visitor) = 0; virtual ~Customer(){} }; //企业客户 class EnterpriseCustomer : public Customer { private: string linkMan; //联系人 string linkTelephone; //联系电话 string registerAddress; //注册地址 public: string& getLinkMan(){return linkMan;} void setLinkMan(string value) { linkMan = value; } string& getLinkTelephone(){return linkTelephone;} void setLinkTelephone(string value) { linkTelephone = value; } string& getRegisterAddress(){return registerAddress;} void setRegisterAddress(string value) { registerAddress = value; } void accept(Visitor* visitor) { //回调访问者对象的相应方法 visitor->visitEnterpriseCustomer(*this); } }; //个人客户的实现 class PersonalCustomer : public Customer { private: string telephone; int age; public: string& getTelephone(){return telephone;} void setTelephone(string value) { telephone = value; } int getAge(){return age;} void setAge(int value) { age = value; } void accept(Visitor* visitor) { //回调访问者对象的相应方法 visitor->visitPersonalCustomer(*this); } }; //************************具体的访问者实现******************************* //实现客户提出服务请求的功能 class ServiceRequestVisitor : public Visitor { public: //企业客户提出的具体服务请求 void visitEnterpriseCustomer(EnterpriseCustomer& ec) { cout <<ec.getName() << "企业提出服务请求" << endl; } //个人客户提出的具体服务请求 void visitPersonalCustomer(PersonalCustomer& pc) { cout <<"客户" <<pc.getName() << "提出服务请求" << endl; } }; //实现对客户偏好的分析 class PredilectionAnalyzeVisitor : public Visitor { public: //针对企业客户的分析 void visitEnterpriseCustomer(EnterpriseCustomer& ec) { //根据以往购买历史、潜在购意向以及客户所在行业的发展趋势 //客户的发展预期等分析 cout <<"现在对企业客户" << ec.getName() << "进行产品偏好分析" << endl; } //针对个人客户的分析 void visitPersonalCustomer(PersonalCustomer& pc) { cout <<"现在对个人客户" << pc.getName() << "进行产品偏好分析" << endl; } }; //*********************************ObjectStructure角色************************* class ObjectStructure { private: list<Customer*> customers; //客户集合 public: //提供给客户端操作的高层接口,具体的功能由客户端传入的访问者决定 void handleRequest(Visitor* visitor) { //循环对象结构中的元素,接受访问 list<Customer*>::iterator iter = customers.begin(); while (iter != customers.end()) { (*iter)->accept(visitor); ++iter; } } //组建对象结构,向对象结构中添加元素 void addElment(Customer* ele) { customers.push_back(ele); } ~ObjectStructure() { customers.clear(); } }; int main() { //创建ObjectStructure对象 ObjectStructure* pOs = new ObjectStructure(); ObjectStructure& os = *pOs; //准备一些测试数据,创建客户对象,并加入到ObjectStructure Customer* cm1 = new EnterpriseCustomer(); cm1->setName("ABC集团"); os.addElment(cm1); Customer* cm2 = new EnterpriseCustomer(); cm2->setName("CDE集团"); os.addElment(cm2); Customer* cm3 = new PersonalCustomer(); cm3->setName("张三"); os.addElment(cm3); //客户提出服务请求,传入服务请求的Visitor ServiceRequestVisitor srVistor; os.handleRequest(&srVistor); //对客户进行偏好分析,传入偏好分析的Visitor PredilectionAnalyzeVisitor paVistor; os.handleRequest(&paVistor); delete cm1; delete cm2; delete cm3; delete pOs; return 0; } /*输出结果: ABC集团企业提出服务请求 CDE集团企业提出服务请求 客户张三提出服务请求 现在对企业客户ABC集团进行产品偏好分析 现在对企业客户CDE集团进行产品偏好分析 现在对个人客户张三进行产品偏好分析 */

2. 二次分派技术(double dispatch)

(1)二次分派技术的原理

  ①分派:根据对象的类型而对方法进行选择,就是分派。静态分派发生在编译时期,分派根据静态类型信息来指定方法,它是一个方法的静态绑定(如方法重载)。动态分派发生在运行时期,是根据接方法所属对象的实际类型来调用方法。动态类型绑定只会体现在方法的调用者身上,而方法的参数类型则会在编译期由编译器决定。

  ②单分派:只根据方法所属的对象的实际类型参数的静态类型来指定调用的方法。

  ③多分派:是根据方法所属的对象的实际类型参数的实际类型来指定调用的方法。

【实验分析】C++的单分派支持及双分派策略的模拟

//问题:C++只支持单分派,即只根据函数名及参数的静态类型(而不是实际类型)来分派。如何进行双分派的模拟?
//解决思路:见后面的分析:

#include <iostream>
using namespace std;

class Problem;        //前向声明
class SpecialProblem; //前向声明

//定义一级支持
class Supporter
{
public:
    virtual void solve(Problem* p)
    {
        cout << "Solution: Level 1 for common problem" << endl;
    }

    virtual void solve(SpecialProblem* sp)
    {
         cout << "Solution: Level 1 for special problem" << endl;
    }
};

//定义资深支持
class SeniorSupporter : public Supporter
{
public:
    virtual void solve(Problem* p)
    {
        cout << "Solution: Level 2 for common problem" << endl;
    }

    virtual void solve(SpecialProblem* sp)
    {
         cout << "Solution: Level 2 for special problem" << endl;
    }
};

//定义一般问题
class Problem
{
public:
    virtual void accept(Supporter* s)
    {
        s->solve(this); //将Problem自身传给s->solve(),这里是关键
    }
};

//定义特殊问题
class SpecialProblem : public Problem
{
public:
    virtual void accept(Supporter* s)
    {
        s->solve(this); //将SpecialProblem自身传给s->solve(),这里是关键
    }
};

int main()
{
    //演示2次分派
    //一般问题和特殊问题
    Problem* p = new Problem();         //注意,是父类指针
    Problem* sp = new SpecialProblem(); //注意,是父类指针

    //资深解决专家
    Supporter* s = new SeniorSupporter();

    cout <<"演示C++语言的单分派现象"<< endl;
    //演示单分派的效果
    //1. 由于solve是个虚函数时,会通过多态找到SeniorSupporter中的solve方法。同理,如果s指向的实际
    //   对象是父类,则会调用父类的solve方法。这是方法的第1次分派。
    //2. 但由于函数的参数类型是静态绑定的,也就是说s和sp的静态类型是Problem*类型,当编译到s->solve(s)
    //   和s->solve(sp)两行时,编译器是根据s和sp的静态类型,固定地绑定到solve(Problem*)的函数,尽管运
    //   行期s和sp的实际类型不同,但C++并不会根据这两个差别而分别去调用SeniorSupporter中两个不同的重载
    //   函数。即不会进行第2次的分派,这就是所谓的C++只支持方法的单分派。
    s->solve(p);   //参数类型是静态绑定,调用solve(Problem*);
    s->solve(sp);  //参数类型是静态绑定,调用solve(Problem*),而不是期望的
                   //根据实际类型去调用solve(SpecialProblem*);

    cout << endl;

    cout <<"演示模拟的双分派策略"<< endl;

    //演示2次分派的效果(模拟双分派的主要依据是Problem和Supporter的具体类型进行分派)
    //思路:
    //1. 再看再上面的s->solve(...)这行调用。由于s类提供两种重载版本的solve函数,假如我们在调用该行时,
    //   编译器就能知道到底传入的是Problem*还是SpecialProblem*参数,那就可以正确的调用到相应的函数了。
    //2. 可如何做到这点呢?解决技巧是反客为主,即让参数作为主调函数,并在参数类中提供一个多态的accept
    //   方法,这个函数最主要目的是为了利用多态得到参数对象的实际类型,因为当参数对象p->access时,
    //   由于accept是虚函数,所以会根据p的类型调用父类或子类的access,这样在access函数里面就可以获得p
    //   对象的实际类型,当得这个实际类型后,就可以传给s->solve函数,可access函数时,如果调用s对象的函数呢?
    //   解决方法很简单,就是在设计access函数时,增加一个接收s对象的参数。这种反客为主的曲线调用就是为了获取
    //   参数的实际类型,这属于方法的第1次分派,然后回调s的solve函数进行第2次分派。
    //3. 访问者模式中的accept方法跟这原理是一样的。Visitor::visit()提供了多个重载的版本,但ObjectStrucure中
    //   保存的元素却Elment的父类指针,而不是实际的类型,如果直接将Element*的指针传入visitor.visit()函数时,
    //   是不能分派到相应的重载版本。因此,在Element类中提供一个accept方法来实现双分派策略。
    p->accept(s);   //反客为主,原理见最上面的分析
    sp->accept(s);  //第1次分派:在运行时根据sp的实际类型找到SpecialProblem::accept(Supporter* s)函数
                    //第2次分派:在上述函数的内部执行s->solve(this)时,会根据s的实际类型找到
                    //           SeniorSupporter::solve(SpecialProblem*),从而正确找到期望的函数。

    return 0;
};
/*输出结果:
演示C++语言的单分派现象
Solution: Level 2 for common problem
Solution: Level 2 for common problem

演示模拟的双分派策略
Solution: Level 2 for common problem
Solution: Level 2 for special problem
*/

(2)访问者模式中的调用通路

  ①Visitor角色之所以能实现“为一系列对象透明地添加新功能”,注意是透明,也就是这一系列的对象是不知道被添加功能的。主要依靠调用通路,即访问者要去visit,而对象那边通过accept,这样就构成一个调用通路

  ②建立通路以后,在accept方法面里,回调visit方法,从而回调到访问者的具体实现上,而这个访问者的具体实现的方法,才是要添加被访问的对象上的新的功能。

(3)二次分派技术在访问者模式中的体现

  ①访问者能够实现在不改变对象结构的情况下,就可以给对象结构中的类增加功能,实现这个效果所使用的核心技术就是两次分派技术

  ②当客户端调用ObjectStructure时,会遍历ObjectStructure中所有的元素,调用这些元素的accept,因为该方法是个虚函数,会通过多态调用到具体的元素的accept方法,让这些元素本身来接受访问,这是请求的第1次分派,即accept方法的多态辩析;在具体的元素对象中实现accept方法的时候,会回调访问者的visit方法并传入this指针,等于第2次分派,请求被分发给访问者来进行处理,真正实现功能的正是访问者的visit方法。即Visit方法的多态辩析

  ③两次分派技术使用客户端的请求不再被静态地绑定在元素对象上,这个时候真正执行什么样的功能同时取决于访问者类型和元素的类型,就算同一种元素类型,只要访问者的类型不同,最终执行功能也会不一样。这样一来,就可以在元素对象不变的情况下,通过改变访问者类型来改变真正执行的功能

【编程实验】为图形类添加新的操作(变式使用Visitor模式)

第25章 行为型模式—访问者模式

#include <iostream>
#include <string>

using namespace std;

class Rectangle;
class Circle;
class Line;

//************************访问者接口************************
class Visitor
{
public:
    //重载了一系列的方法,将Shape的子类作为参数,但是这个Visit也存在改变,
    //这是一个弊端。注意Visit的参数是具体的类型,而不是父类的。因为要为
    //不同的元素增加不同的操作。如果写成父类的,那就必须在Visit函数内进行
    //区别,会造成函数不够过于庞大、职责不清,且不够灵活.
    virtual void visit(Rectangle* shape) = 0; //#1
    virtual void visit(Circle* shape) = 0;    //#2
    virtual void visit(Line* shape) = 0;      //#3

};

//**********************************被访问的对象(Element)***********************
//绘制图形
class Shape
{
public:
    //假设目前只有一个功能draw。
    virtual void draw() = 0;
    /*问题:1.现在要在基类中添加一个将图形整体移动的方法,如
            moveTo(Point p),则需要改接口。而接口的改变意味
            着各个子类都要随之变化。
            2. 还有一种可能,在基类添加方法,可有些子类并不需要
            这样的方法。
    */

    //预测将来可能会为该类添加新的功能(如moveTo(Point p))
    //传入己经实现新功能的访问者
    virtual void accept(Visitor* v) = 0;
};

class Rectangle : public Shape
{
public:
    void draw()
    {
        cout <<"Rectangle()::draw()" << endl;
    }

    void accept(Visitor* v)
    {
        v->visit(this); //静态绑定,注意this的类型是Rectangle*,所以
                        //调用Visitor::Visit(Rectangle* shape)
    }
};

class Circle : public Shape
{
public:
    void draw()
    {
        cout <<"Circle()::draw()" << endl;
    }
    void accept(Visitor* v)
    {
        v->visit(this); //静态绑定,注意this的类型是Circle*,所以
                        //调用Visitor::Visit(Circle* shape)
    }
};

class Line : public Shape
{
public:
    void draw()
    {
         cout <<"Line()::draw()" << endl;
    }
    void accept(Visitor* v)
    {
        v->visit(this); //静态绑定,注意this的类型是Line*,所以
                        //调用Visitor::Visit(Line* shape)
    }
};

//具体的访问者,为各个类增加新的方法,如MoveTo
class MoveVisitor : public Visitor
{
public:
    void visit(Rectangle* shape)
    {
        cout << "新增Rectangle整体移动的方法" << endl;
    }
    void visit(Circle* shape)
    {
        cout << "新增Circle整体移动的方法" << endl;
    }
    void visit(Line* shape)
    {
        cout << "新增Line整体移动的方法" << endl;
    }
};

//**********************ObjectStructure角色***************************
//这里只为单个类增加新行为
class App
{
    Visitor* visitor;
public:
    App(Visitor* v)
    {
        this->visitor = v;
    }
    void process(Shape* shape)
    {
        //两次分派:
        //1.由于Shape*父类指针,传入具体子类时Accept会发生多态。
        //  找到子类的accept方法的调用对象。
        //2. 分派visit方法,发生在visitor->visit(this)方法中。
        //   会根据this指针所指的具体对象的不同而选择不同重载版本的vist函数。
        shape->accept(visitor);
    }
};

int main()
{
    //创建对象
    Rectangle rt;
    Circle    cc;

    //创建要添加的新方法
    Visitor* vt = new MoveVisitor();

    //动态添加方法
    App* app = new App(vt);

    //为矩形添加新方法(移动操作)
    app->process(&rt);

    //为圆添加新方法(移动操作)
    app->process(&cc);

    //线对象就不做演示了...

    delete vt;
    delete app;

    return 0;
}
/*输出结果:
新增Rectangle整体移动的方法
新增Circle整体移动的方法
*/

3. 操作组合对象结构

(1)组合对象结构一般是使用组合模式来构建的,可以结合访问者模式来为各个元素添加新的功能

(2)思路:首先把组合对象结构中的功能方法分离出来,然后把这些方法分别实现成访问者对象,通过访问者模式添加到组合的对象结构中去。

4. 谁负再遍历所有元素对象

(1)有ObjectStructure对象时

  ①如果元素的对象结构是通过集合来组织的,就可以直接在ObjectStructure对集合进行迭代,然后对每一个元素调用accept方法。

  ②如果元素的对象结构是通过组合模式来组织的,通常可以构成对象树。这种情况下就不需要在ObjectStructure中迭代了。通常的做法是在组合对象accept方法中递归遍历它的子元素,然后调用子元素的accept方法

(2)没有ObjectStructure对象时

  ①实际开发中,可能不需要ObjectStructure对象,那就只有一个被访问对象,因为只有一个被访问对象,当然就不需要使用ObjectStructure来组合和迭代了,只需要调用这个对象就可以了。

  ②当然也有可能是通过组合模式构造出来的对象树,这时从客户端角度看,他访问的其实也就是一个对象,所以也没必要有ObjectStructure。这时客户端可直接调用组合对象结构的根元素的accept方法

(3)有时,遍历元素的方法也可以放到访问者中,当然也是需要递归遍历它的子元素的,出现这种情况的主要原因是,想在访问者中实现特别复杂的遍历,访问者的实现依赖于对象树的操作结果,如用树形结构输出带有缩进格式的节点和叶子的名称

【编程实验】员工信息分门别类查询和统计

第25章 行为型模式—访问者模式

//行为型模式——访问者模式
//场景:公司人事信息查询
/*
说明:
1. 公司的人员组织机构树,包括普通员工和部门经理两种类型
2. 要求该机构提供不同格式的报表,即普通员工和部门经理的报表信息有所不同
3. 能分别统计公司普通员工的工资总额和部门经理的总和及整个公司的工资支付总额
4. 预留其它可以功能需求,如今天报表内容可能有所变化。如何做到遵循开闭原则?
*/
#include <iostream>
#include <string>
#include <list>
#include <sstream> //for istringstream/ostringstream

using namespace std;

//
class CommonEmployee; //前置声明,普通员工类
class Manager;        //前置声明,管理员类

class IVisitor
{
public:
    virtual void visit(CommonEmployee& ce) = 0;
    virtual void visit(Manager& mn) = 0;
    //virtual int getTotalSalary() = 0;
};

//展示报表的接口
class IShowVisitor : public IVisitor
{
public:
    //展示报表
    virtual void report() = 0;
};

//汇总表接口
class ITotalVisitor : public IVisitor
{
public:
    //统计所有员工工资总和
    virtual void totalSalary() = 0;
};

//************************************Element角色**********************************
//抽象员工
class Employee
{
private:
    string name; //姓名
    int salary;  //薪水
    string sex;     //性别
public:
    string& getName(){return name;}
    void setName(string value)
    {
        this->name = value;
    }

    int getSalary(){return salary;}
    void setSalary(int value)
    {
        salary = value;
    }

    string getSex(){return sex;}
    void setSex(string value)
    {
        sex = value;
    }

    //允许一个访问者访问
    virtual void accept(IVisitor* visitor) = 0;

    virtual ~Employee(){}
};

//普通员工
class CommonEmployee : public Employee
{
private:
    string job; //工作,这个成员量后面要使用它
public:
    string& getJob(){return job;}
    void setJob(string value)
    {
        job = value;
    }

    void accept(IVisitor* visitor)
    {
        visitor->visit(*this);
    }
};

//管理阶层
class Manager : public Employee
{
private:
    string performance; //业绩
public:
    string& getPerformance(){return performance;}
    void setPerformance(string value)
    {
        performance = value;
    }

    void accept(IVisitor* visitor)
    {
        visitor->visit(*this);
    }
};

//***********************************具体的访问者************************************
//具体的展示表的类
class ShowVisitor : public IShowVisitor
{
private:
    string info;
public:
    void report()
    {
        cout << info << endl;
    }

    //访问普通员工,组装信息
    void visit(CommonEmployee& ce)
    {
        info += getBaseInfo(ce) + "工作:" + ce.getJob() +"tn";
    }

    //访问经理,然后组装信息
    void visit(Manager& mn)
    {
        info += getBaseInfo(mn) + "业绩:" + mn.getPerformance() + "tn";
    }

    //组装出基本信息
    string getBaseInfo(Employee& emp)
    {
        ostringstream oss;
        oss << emp.getSalary();
        string ret = "姓名:" + emp.getName() + "t" +
                     "性别:" + emp.getSex() + "t" +
                     "薪水:" + oss.str() + "t";
        return ret;
    }
};

//具体的汇总表
class TotalVistor : public ITotalVisitor
{
private:
    //部门经理的工资系数是5
    static const int MANAGER_COEFFICIENT = 5;
    //员工的工资系数是2
    static const int COMMONEMPLOYEE_COEFFICIENT = 2;
        //普通员工的工资总和
    int commonTotalSalary;
    //部门经理的工资总和
    int managerTotalSalary;

public:
    TotalVistor()
    {
        commonTotalSalary = 0;
        managerTotalSalary = 0;
    }

    //访问普通员工,计算工资总额
    void visit(CommonEmployee& emp)
    {
        commonTotalSalary += emp.getSalary() * COMMONEMPLOYEE_COEFFICIENT;
    }

    //访问部门经理,计算工资总额
    void visit(Manager& mn)
    {
        managerTotalSalary += mn.getSalary() * MANAGER_COEFFICIENT;
    }

    //计算总工资
    void totalSalary()
    {
        cout <<"本公司的月工资总额是:"  << commonTotalSalary + managerTotalSalary << endl;
    }
};

//*****************************ObjectStructure角色***********************
class ObjectStructure
{
private:
    list<Employee*> emps;
public:
    ObjectStructure()
    {
        //初始化,产生3个员工
        CommonEmployee* emp1 = new CommonEmployee();
        emp1->setName("张三");
        emp1->setSalary(1800);
        emp1->setJob("C++工程师");
        emp1->setSex("");
        emps.push_back(emp1);

        CommonEmployee* emp2 = new CommonEmployee();
        emp2->setName("李四");
        emp2->setSalary(1900);
        emp2->setJob("页面美工");
        emp2->setSex("");
        emps.push_back(emp2);

        Manager* emp3 = new Manager();
        emp3->setName("王五");
        emp3->setPerformance("基本上是负值,只会拍马屁");
        emp3->setSalary(18750);
        emp3->setSex("");
        emps.push_back(emp3);
    }
    ~ObjectStructure()
    {
        list<Employee*>::iterator iter = emps.begin();
        while(iter != emps.end())
        {
            if(*iter != NULL)
                delete (*iter);
            ++iter;
        }
        emps.clear();
    }

    void handleRequest(IVisitor* visitor)
    {
        list<Employee*>::iterator iter = emps.begin();
        while(iter != emps.end())
        {
            if(*iter != NULL)
                (*iter)->accept(visitor);

            ++iter;
        }
    }
};

int main()
{
    //创建公司员工信息
    ObjectStructure os;

    //展示报表的访问者
    IShowVisitor* showVisitor = new ShowVisitor();
    os.handleRequest(showVisitor);
    showVisitor->report();

    //统计工资总额
    ITotalVisitor* totalVisitor = new TotalVistor();
    os.handleRequest(totalVisitor);
    totalVisitor->totalSalary();

    return 0;
}
/*输出结果:
姓名:张三       性别:男 薪水:1800       工作:C++工程师
姓名:李四       性别:女 薪水:1900       工作:页面美工
姓名:王五       性别:男 薪水:18750      业绩:基本上是负值,只会拍马屁

本公司的月工资总额是:101150
*/

5. 访问者模式的优缺点

(1)优点

  ①好的扩展性。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能

  ②好的复用性。可以通过访问者来定义整个对象结构通用的功能,从而提高复用程序。

  ③分离无关行为。可以通过访问者分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

(2)缺点

  ①对象结构变化很困难。在于增添新的Element子类的时候会导致Visitor类发生改变,而且随着Element子类的增加,Visitor类会越来越庞大

  ②破坏封装。访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructure,这破坏了对象的封装性。

6. 访问者模式的应用场景

(1)如果想对一个对象结构实施一些依赖于对象结构中具体类的操作,可以使用访问者模式。

(2)如果想对一个对象结构中的各个元素进行很多不同的而且不相关的操作,为了避免这些操作使类变得杂乱,可以使用访问者模式。把这些操作分散到不同的访问对象中去,每个访问者对象实现同一类功能。

(3)如果对象结构较稳定(很少变动,即Element的子类类型和数量固定的情况下,但是需要经常给对象结构中的元素对象定义新的操作

7. 相关模式

(1)访问者模式和组合模式

  访问者模式为组合对象预留下扩展功能的接口,使用组合模式的对象结构添加功能非常容易。

(2)访问者模式和装饰模式

  ①表面上看这两个模式的功能有些相似,都能够在不修改原有对象结构的情况下修改原对象的功能。

  ②但装饰模式更多的是实现对己有功能的加强、修改或者完全重新实现。而访问者模式更多的是实现为对象结构添加新的功能

(3)访问者模式和解释器模式

  解释器模式在构建抽象语法树的时候,是使用组合模式来构建的,也就是说解释器模式解释并执行的抽象语法树是一个组合对象结构,这时可以使用访问者模式为解释器增加新的功能,实现为同一对象结构的不同解释和执行的功能。因此在使用解释器模式时,通常会结合访问者模式来使用。

原文链接: https://www.cnblogs.com/5iedu/p/5649013.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    第25章 行为型模式—访问者模式

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/394089

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月3日 下午3:37
下一篇 2023年4月3日 下午3:37

相关推荐