C++实现的访问者模式

访问者模式是一种设计模式,它分离了对象的数据结构和作用于结构上的操作,使得在不改变元素类的前提下,可以为对象结构动态添加新操作。文章详细介绍了访问者模式的定义、解读、类图、结构、生活实例、C++代码实现、优点和适用场景,展示了如何在C++中应用访问者模式。

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

二、解读
“某对象结构”,这个结构中可能会有很多不同类型的对象。针对不同对象会有不同的操作。
那么一般情况下,要操作这个对象结构中的不同对象,我们可能会遍历集合中的每个对象,然后根据每个对象的类型来做具体的操作。这会很复杂,因为我们一定会用很多if…else来检查对象类型。可想而知,代码一定会很杂乱。耦合性很强。
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。

三、类图
这里写图片描述

四、结构
抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。它的方法个数理论上来讲与元素个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变(不能改变的意思是说,如果元素类的个数经常改变,则说明不适合使用访问者模式)。
具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
抽象节点(Element)角色:声明一个接受操作,接受一个访问者对象作为一个参量。其意义是指,每一个元素都要可以被访问者访问。
具体节点(ConcreteElement)角色:实现了抽象元素所规定的接受操作。
结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。
访问者模式主要分为两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,主要用于什么一些操作。一个是元素层次结构,提供了抽象元素和具体元素,主要用于声明Accept操作。

五、生活中的例子
一台电脑由很多部件组成,有显示屏、主机、键盘、鼠标等外设。不同身份的人对这台电脑的部件会有不同的操作。消费者可能就是简单地看看各个部件的价格、商标、参数。维修员则会对部件进行检查。
在这里,消费者和维修员就是具体访问者角色,显示屏、主机、键盘、鼠标等就是具体节点角色,电脑这一个整体就是结构对象角色。
此处用访问者模式,代码如下:

六、C++代码
(一)抽象节点

// 部件基类 Part.h
#ifndef VISITORDEMO_PART_H
#define VISITORDEMO_PART_H
class Visitor;
class Part{
public:
    virtual void accept(Visitor *visitor) = 0;
};
#endif //VISITORDEMO_PART_H

(二)具体节点

// 显示屏类,具体节点。 Monitor.h
#ifndef VISITORDEMO_MONITOR_H
#define VISITORDEMO_MONITOR_H
#include "Part.h"
#include "Visitor.h"
#include <iostream>
using namespace std;
class Monitor: public Part {
private:
    string brand;
    float price;
public:
    Monitor(string brand,float price);
    void accept(Visitor *visitor) override ;
    string getBrand();
    float getPrice();
};
#endif //VISITORDEMO_MONITOR_H
//Monitor.cpp
#include "../header/Monitor.h"
Monitor::Monitor(string brand, float price) {
    this->brand = brand;
    this->price = price;
}
void Monitor::accept(Visitor *visitor) {
    visitor->visit(this);
}
string Monitor::getBrand() {
    return brand;
}
float Monitor::getPrice() {
    return price;
}
// 主机类,具体节点。 Mainframe.h
#ifndef VISITORDEMO_MAINFRAME_H
#define VISITORDEMO_MAINFRAME_H
#include "Part.h"
#include "Visitor.h"
#include <iostream>
using namespace std;
class Mainframe: public Part {
private:
    string brand;
    float price;
public:
    Mainframe(string brand, float price);
    void accept(Visitor *visitor) override ;
    string getBrand();
    float getPrice();
};
#endif //VISITORDEMO_MAINFRAME_H
//Mainframe.cpp
#include "../header/Mainframe.h"
Mainframe::Mainframe(string brand, float price) {
    this->brand = brand;
    this->price = price;
}
void Mainframe::accept(Visitor *visitor) {
    visitor->visit(this);
}
string Mainframe::getBrand() {
    return brand;
}
float Mainframe::getPrice() {
    return price;
}

(三)抽象访问者

// 抽象访问者。 Visitor.h
#ifndef VISITORDEMO_VISITOR_H
#define VISITORDEMO_VISITOR_H
class Monitor;
class Mainframe;
class Visitor{
public:
    virtual void visit(Monitor *monitor) {}
    virtual void visit(Mainframe *mainframe) {}
};
#endif //VISITORDEMO_VISITOR_H

(四)具体访问者

// 消费者类,是一个具体的访问者。 Consumer.h
#ifndef VISITORDEMO_CONSUMER_H
#define VISITORDEMO_CONSUMER_H
#include "Visitor.h"
#include "Monitor.h"
#include "Mainframe.h"
class Consumer: public Visitor {
private:
    float sum;
public:
    void visit(Monitor *monitor) override ;
    void visit(Mainframe *mainframe) override ;
    float getSum();
};
#endif //VISITORDEMO_CONSUMER_H
//Consumer.cpp
#include "../header/Consumer.h"
void Consumer::visit(Monitor *monitor) {
    sum += monitor->getPrice();
    cout<<"顾客查看了下显示器,品牌是:"<<monitor->getBrand()<<",价格是:"<<monitor->getPrice()<<endl;
}
void Consumer::visit(Mainframe *mainframe) {
    sum += mainframe->getPrice();
    cout<<"顾客查看了下主机,品牌是:"<<mainframe->getBrand()<<",价格是:"<<mainframe->getPrice()<<endl;
}
float Consumer::getSum() {
    cout<<"顾客算了下,这台电脑的总价是:"<<sum<<endl;
}
// 维修员,是一个具体的访问者。 Repairman.h
#ifndef VISITORDEMO_REPAIRMAN_H
#define VISITORDEMO_REPAIRMAN_H
#include "Visitor.h"
#include "Monitor.h"
#include "Mainframe.h"
class Repairman: public Visitor {
public:
    void visit(Monitor *monitor) override ;
    void visit(Mainframe *mainframe) override ;
};
#endif //VISITORDEMO_REPAIRMAN_H
//Repairman.cpp
#include "../header/Repairman.h"
void Repairman::visit(Monitor *monitor) {
    cout<<"维修员在维修显示器"<<endl;
}
void Repairman::visit(Mainframe *mainframe) {
    cout<<"维修员在维修主机"<<endl;
}

(五)结构对象角色

// Created by huxijie on 16-11-9.
// 电脑类。是一个结构对象角色。Computer.h
#ifndef VISITORDEMO_COMPUTER_H
#define VISITORDEMO_COMPUTER_H
#include "Visitor.h"
#include "Part.h"
#include <iostream>
#include <list>
using namespace std;
class Monitor;
class Mainframe;
class Computer {
private:
    list<Part *> parts;
public:
    void attach(Part *part);
    void detach(Part *part);
    void accept(Visitor *visitor);
};
#endif //VISITORDEMO_COMPUTER_H
// Created by huxijie on 16-11-9.
//Computer.cpp
#include "../header/Computer.h"
using namespace std;
void Computer::attach(Part *part) {
    parts.push_back(part);
}
void Computer::detach(Part *part) {
    parts.remove(part);
}
void Computer::accept(Visitor *visitor) {
    for (list<Part*>::iterator it = parts.begin(); it != parts.end(); it++) {
        (*it)->accept(visitor);
    }
}

(六)主函数

#include <iostream>
#include "header/Monitor.h"
#include "header/Mainframe.h"
#include "header/Computer.h"
#include "header/Consumer.h"
#include "header/Repairman.h"
using namespace std;
int main() {
    Computer *computer = new Computer;
    computer->attach(new Monitor("华硕", 1699));
    computer->attach(new Mainframe("极途", 4699));
    Consumer *consumer = new Consumer;
    Repairman *repairman = new Repairman;
    computer->accept(consumer);
    computer->accept(repairman);
    return 0;
}

(七)运行结果
这里写图片描述
七、双向分派
双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别(Run time type),还要根据参数的运行时区别。
首先在客户程序中将具体访问者模式作为参数传递给具体元素角色。这便完成了一次分派。
进入具体元素角色后,具体元素角色调用作为参数的具体访问者模式中的visit方法,同时将自己(this)作为参数传递进去。具体访问者模式再根据参数的不同来选择方法来执行。这便完成了第二次分派。

八、优点
1、扩展性良好。访问者模式使得增加新的操作变得很容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,增加新的操作会很复杂(在对象类中加大量if…else)。而使用访问者模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。
2、访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
3、不同访问者可以对同一种元素有不同的操作。
4、积累状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以在访问的过程中将执行操作的状态积累在自己内部,而不是分散到很多的节点对象中。这是有益于系统维护的优点。

九、缺点
1、增加新的节点类变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
2、破坏封装。如果将访问行为放在各个元素中,则可以不暴露元素的内部结构和状态。但访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有节点对象的要求:它们必须暴露一些自己的操作和内部状态。不然,访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这也是破坏封装的。

十、适用场景
1、 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2、假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
3、当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4、定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,元素类数目不确定的情况下,应该慎用访问者模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值