设计模式---行为变化模式之访问器模式(Visitor)
一:概念
访问者模式,是行为模式之一,它分离对象的数据和行为,使用Visitor模式,可以不修改已有类的情况下,增加新的操作角色和职责。
二:动机
在软件构建的过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法)。如果直接在类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
三:代码讲解
(一)原代码
#include <iostream>
using namespace std; class Visitor; class Element
{
public:
virtual void Func1() = ; virtual ~Element(){}
}; class ElementA : public Element
{
public:
void Func1() override{
//...
}
}; class ElementB : public Element
{
public:
void Func1() override{
//***
}
};
需求:想要增加一个新的功能
#include <iostream>
using namespace std; class Visitor; class Element
{
public:
virtual void Func1() = ; virtual void Func2(int data)=0; //定义虚方法,后面继承父类的都要一一实现
virtual void Func3(int data)=;
//... 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 {
//***
}
};
这不是一个好的方法,在部署后,我们再次修改,此时的代价是十分高的,违背了开闭原则
(二)visitor模式---->前提:能够预料到未来可能会为这整个类层次结构添加新的操作,但是我不知道要加多少操作,什么操作
1.预先设计Element基类
class Visitor; class Element
{
public:
virtual void accept(Visitor& visitor) = 0; //第一次多态辨析
//accept方法为将来埋下伏笔,将来要添加新的功能,加到这里去找他,进行绑定visitor
virtual ~Element(){}
};
2.完善visitor基类
class Visitor{
public:
virtual void visitElementA(ElementA& element) = 0; //针对每一个子类生成一个方法对应
virtual void visitElementB(ElementB& element) = 0;
virtual ~Visitor(){}
};
3.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); //第二次多态辨析
} };
=========================上面实现了:预先的设计了我将来可能会增加的新的操作=============================
4.将来,要为前面的整个类层次结构添加新的操作,添加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;
}
};
5.操作绑定
int main()
{
Visitor2 visitor;
ElementB elementB;
elementB.accept(visitor);// double dispatch 将visitor2中的新的visitElementA操作添加到elementB中 ElementA elementA;
elementA.accept(visitor); //将Visitor2中新的visitElementB新操作添加到了elementA中 return ;
}
四:模式定义
表示一个作用与某对像结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)。
——《设计模式》GoF
五:类图(结构)

visitor要预先知道(依赖于)我们具体的ELement子类,所以我们要提前设置好其子类,之后基本不会修改我们修改的只有visitor扩展子类。
所以Element子类是稳定的
(必须提前确定下来,但是常常确定不了,我们一增加element子类,visitor基类就要做出相应改变,子类也是,全部的稳定性都混乱了,打破了开闭原则,全部都要重新编译),
visitor子类是变化的
六:要点总结
(一)Vistor模式通过所谓的双重分发(double dispatch)来实现在不更改(不添加新的操作-编译时)Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作(支持变化)。
(二)所谓双重分发即Vistor模式中间包括了两个多态分发(注意其中的多态机制):第一个accept方法的多态解析;第二个为visitElementX方法的多态辨析。
(三)Visitor模式最大的缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却进场面临频繁改动”。
七:案例实现(公园功能扩展)
比如有一个公园,有一到多个不同的组成部分;多个公园,每个公园存在多个访问者:清洁工负责打扫公园的部分,公园的管理者负责检点各项事务是否完成,上级领导可以视察公园,扩建等等。
也就是说,对于同一个公园,不同的访问者有不同的行为操作,而且访问者的种类也可能需要根据时间的推移而变化(行为的扩展性)。
根据软件设计的开闭原则(对修改关闭,对扩展开放),我们怎么样实现这种需求呢?
(一)实现访问者基类
class ParkElement; class Visitor //Visitor是针对每个element的子类,其方法也是针对
{
public:
virtual void visitParkA(ParkElement* park) = ;
virtual void visitParkB(ParkElement* park) = ; virtual ~Visitor(){}
};
(二)实现公园基类
class ParkElement
{
public:
void Clean()
{
cout << "clean part" << endl;
} void Play()
{
cout << "play in part" << endl;
} virtual void accept(Visitor* v) = ;
};
(三)实现子类具体公园
class ParkA:public ParkElement
{
public:
virtual void accept(Visitor* v)
{
v->visitParkA(this);
}
}; class ParkB :public ParkElement
{
public:
virtual void accept(Visitor* v)
{
v->visitParkB(this);
}
};
(四)针对具体公园子类扩展功能
//扩展,这里扩展了两个功能
class Visitor1 :public Visitor
{
public:
virtual void visitParkA(ParkElement* park)
{
cout << "leader check parkA health" << endl;
} virtual void visitParkB(ParkElement* park)
{
cout << "leader check parkB health" << endl;
}
}; class Visitor2 :public Visitor
{
public:
virtual void visitParkA(ParkElement* park)
{
cout << "leader extend parkA area" << endl;
} virtual void visitParkB(ParkElement* park)
{
cout << "leader extend parkB area" << endl;
}
};
(五)进行扩展绑定
void main()
{
Visitor1 visitor;
ParkA pA,pB;
pA.accept(&visitor);
pB.accept(&visitor); system("pause");
return;
}

#include <iostream>
#include <list>
#include <string>
using namespace std; class ParkElement; class Visitor //Visitor是针对每个element的子类,其方法也是针对
{
public:
virtual void visitParkA(ParkElement* park) = ;
virtual void visitParkB(ParkElement* park) = ; virtual ~Visitor(){}
}; class ParkElement
{
public:
void Clean()
{
cout << "clean part" << endl;
} void Play()
{
cout << "play in part" << endl;
} virtual void accept(Visitor* v) = ;
}; class ParkA:public ParkElement
{
public:
virtual void accept(Visitor* v)
{
v->visitParkA(this);
}
}; class ParkB :public ParkElement
{
public:
virtual void accept(Visitor* v)
{
v->visitParkB(this);
}
}; //扩展
class Visitor1 :public Visitor
{
public:
virtual void visitParkA(ParkElement* park)
{
cout << "leader check parkA health" << endl;
} virtual void visitParkB(ParkElement* park)
{
cout << "leader check parkB health" << endl;
}
}; class Visitor2 :public Visitor
{
public:
virtual void visitParkA(ParkElement* park)
{
cout << "leader extend parkA area" << endl;
} virtual void visitParkB(ParkElement* park)
{
cout << "leader extend parkB area" << endl;
}
}; void main()
{
Visitor1 visitor;
ParkA pA,pB;
pA.accept(&visitor);
pB.accept(&visitor); system("pause");
return;
}
全部代码
设计模式---行为变化模式之访问器模式(Visitor)的更多相关文章
- Java进阶篇设计模式之五-----外观模式和装饰器模式
前言 在上一篇中我们学习了结构型模式的适配器模式和桥接模式.本篇则来学习下结构型模式的外观模式和装饰器模式. 外观模式 简介 外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口.这 ...
- Java设计模式之五 ----- 外观模式和装饰器模式
前言 在上一篇中我们学习了结构型模式的适配器模式和桥接模式.本篇则来学习下结构型模式的外观模式和装饰器模式. 外观模式 简介 外观模式隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口.这 ...
- 深入探索Java设计模式(三)之装饰器模式
装饰器模式使你可以在运行时使用类似于对象组成的技术来装饰类.这在我们希望实例化具有新职责的对象而无需对基础类进行任何代码更改的情况下尤其有用.本文是在学习完优锐课JAVA架构VIP课程—[框架源码专题 ...
- IOS设计模式之二(门面模式,装饰器模式)
本文原文请见:http://www.raywenderlich.com/46988/ios-design-patterns. 由 @krq_tiger(http://weibo.com/xmuzyq) ...
- 设计模式---领域规则模式之解析器模式(Interpreter)
前提:领域规则模式 在特定领域内,某些变化虽然频繁,但可以抽象为某种规则.这时候,结合特定领域,将问题抽象为语法规则,从而给出该领域下的一般性解决方案. 典型模式 解析器模式:Interpreter ...
- 设计模式---对象创建模式之构建器模式(Builder)
一:概念 Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种.Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象 ...
- 面向对象程序设计(OOP设计模式)-结构型模式之装饰器模式的应用与实现
课程名称:程序设计方法学 实验4:OOP设计模式-结构型模式的应用与实现 时间:2015年11月18日星期三,第3.4节 地点:理1#208 一.实验目的 加深对结构型设计模式的理解以及在开发中的实际 ...
- JS设计模式(三) 数据访问对象模式
引言 HTML5 提供了两种在客户端存储数据的新方法:localStorage.sessionStorage,他们是Web Storage API 提供的两种存储机制,区别在于前者属于永久性存储,而后 ...
- Java设计模式(七)Decorate装饰器模式
一.场景描述 (一)问题 系统中最初使用Crystal Report(水晶报表)工具生成报表,并将报表发送给客户端查看,此时定义一CrystalReport工具类即可完成水晶报表的生成工作. 后续报表 ...
随机推荐
- HDU1251 字典树板子题
题意:中文题,统计以某字符串作为前缀的字符串个数 刚学字典树,理解起来十分简单,就是维护一个多叉树,这里用的是链表版本,后面就用的是数组版本了,个人更喜欢数组版本,这里的链表版本就因为 莫名其妙的错误 ...
- HDU2204 Eddy's爱好
题意:给你一个正整数N,确定在1到N之间有多少个可以表示成M^K(K>1)的数. 解析:一个数N 开K次根后得到M 则小于M的所有数的K次方一定小于N 因为任何一个合数都能分解为素数的乘积 所 ...
- IOS端 margin-top 和 margin-bottom 使用负数时的区别
有以下html代码 <div style="width: 30%;" class="shang"> 1 </div> <div s ...
- restfull api交互常用状态码
2xx (成功类别) 200 Ok:标准的 HTTP 响应,表示 GET.PUT 或 POST 的处理成功. 201 Created:在创建新实例时,应返回此状态代码.例如,使用 POST 方法创建一 ...
- 常用CSS样式速查
writing-mode: vertical-lr; -webkit-writing-mode: vertical-lr; -ms-writing-mode: vertical-lr; 作用 CSS ...
- Picture POJ - 1177 (扫描线)
扫描线求周长,可以看成两条线,一条扫x轴,一条扫y轴,然后这两天线扫过去的 周长加起来,就是周长了 #include<map> #include<set> #include&l ...
- PHP基础学习----函数
一.函数的定义 function 函数名([参数1,参数2,参数3,...]){ 函数体: [return 返回值;] } //函数调用 函数名([参数1,参数2,参数3,...]): 二.可变函数 ...
- Libre OJ 130、131、132 (树状数组 单点修改、区间查询 -> 区间修改,单点查询 -> 区间修改,区间查询)
这三题均可以用树状数组.分块或线段树来做 #130. 树状数组 1 :单点修改,区间查询 题目链接:https://loj.ac/problem/130 题目描述 这是一道模板题. 给定数列 a[1] ...
- docker file 示例
报错 Cannot connect to the Docker daemon. Is the docker daemon running on this host? 这个错误只要输入docker -d ...
- 【css技能提升】完美的 Sticky Footer 布局
在总结之前所做的项目时,遇到过下面这种情况. 在主体内容不足够多或者未完全加载出来之前,就会导致出现左边的这种情况,原因是因为没有足够的垂直空间使得页脚推到浏览器窗口最底部.但是,我们期望的效果是页脚 ...