设计模式之访问者(visitor)模式
在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量计算总价,而药房工作人员根据药品名称和数量准备药品,如下图所示。

在软件开发中,有时候也需要处理像处方单这样的集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式。在设计模式中,有一种模式可以满足上述要求,其模式动机就是以不同的方式操作复杂对象结构,该模式就是访问者模式。
访问者模式是一个可以考虑用来解决的方案,它可以在一定程度上解决上述问题(大部分问题)。
一 访问者模式概述
1.1 访问者模式简介
访问者模式是一种较为复杂的行为型模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。例如:处方单中的各种药品信息就是被访问的元素,而划价人员和药房工作人员就是访问者。访问者模式可以使得用户在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。
访问者(Visitor)模式:提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
1.2 需求背景
Background:M公司开发部想要为某企业开发一个OA系统,在该OA系统中包含一个员工信息管理子系统,该企业包括正式员工和临时工,每周HR部门和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等等。该企业的基本制度如下:
(1)正式员工(Full time Employee)每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假锁扣工资以80元/小时计算,直到基本工资扣除到0为止。除了记录实际工作时间外,HR部需要记录加班时长或请假时长,作为员工平时表现的一项依据。
(2)临时员工(Part time Employee)每周工作时间不固定,基本工资按照小时计算,不同岗位的临时工小时工资不同。HR部只需要记录实际工作时间。
HR人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,HR人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。
1.3 类图

1.4 代码实现
1.4.1 抽象员工类
// 被雇佣者类
#pragma once #include <string>
using namespace std; class IDepartment;
// 抽象雇佣者类
class IEmployee
{
public:
IEmployee(){}
public:
virtual ~IEmployee(){}
virtual void Accept(IDepartment *pDepartment) = ;
};
1.4.2 全职员工类
class CFullTimeEmployee : public IEmployee
{
public:
CFullTimeEmployee();
CFullTimeEmployee(string strName, double dbWeeklyWage, int nWeeklyTime);
~CFullTimeEmployee(); void Accept(IDepartment *pDepartment);
string GetName()
{
return m_strName;
} double GetWeeklyWage()
{
return m_dbWeeklyWage;
} int GetWeeklyTime()
{
return m_nWeeklyTime;
} private:
string m_strName;
double m_dbWeeklyWage;
int m_nWeeklyTime;
};
CFullTimeEmployee::CFullTimeEmployee()
{ } CFullTimeEmployee::CFullTimeEmployee(string strName, double dbWeeklyWage, int nWeeklyTime)
:m_strName(strName),m_dbWeeklyWage(dbWeeklyWage),m_nWeeklyTime(nWeeklyTime)
{
} CFullTimeEmployee::~CFullTimeEmployee()
{ } void CFullTimeEmployee::Accept(IDepartment *pDepartment)
{
pDepartment->Visit(this);
}
1.4.3 临时员工类
class CPartTimeEmployee : public IEmployee
{
public:
CPartTimeEmployee();
CPartTimeEmployee(string strName, double dbWeeklyWage, int nWeeklyTime);
~CPartTimeEmployee();
void Accept(IDepartment *pDepartment);
string GetName()
{
return m_strName;
} double GetWeeklyWage()
{
return m_dbWeeklyWage;
} int GetWeeklyTime()
{
return m_nWeeklyTime;
} private:
string m_strName;
double m_dbWeeklyWage;
int m_nWeeklyTime;
};
CPartTimeEmployee::CPartTimeEmployee()
{ }
CPartTimeEmployee::CPartTimeEmployee(string strName, double dbWeeklyWage, int nWeeklyTime)
:m_strName(strName),m_dbWeeklyWage(dbWeeklyWage),m_nWeeklyTime(nWeeklyTime)
{
}
CPartTimeEmployee::~CPartTimeEmployee()
{
} void CPartTimeEmployee::Accept(IDepartment *pDepartment)
{
pDepartment->Visit(this);
}
1.4.4 抽象部门类
// 访问者类
#pragma once
#include "employee.h" #include <string>
#include <iostream>
using namespace std; class IDepartment
{
protected:
IDepartment(){}
public:
virtual ~IDepartment(){}
virtual void Visit(CFullTimeEmployee *pFullTimeEmployee) = ;
virtual void Visit(CPartTimeEmployee *pPartTimeEmployee) = ;
};
1.4.5 财务部门类
class CFinanceDepartment : public IDepartment
{
public:
CFinanceDepartment(){}
~CFinanceDepartment(){} void Visit(CFullTimeEmployee *pFullTimeEmployee)
{
string strName = pFullTimeEmployee->GetName();
double dbWeeklyWage = pFullTimeEmployee->GetWeeklyWage();
int nWeeklyTime = pFullTimeEmployee->GetWeeklyTime(); if (nWeeklyTime >= )
{
dbWeeklyWage = dbWeeklyWage + (nWeeklyTime - ) * ;
}
else
{
dbWeeklyWage = dbWeeklyWage - ( - nWeeklyTime) * ;
if (dbWeeklyWage < )
{
dbWeeklyWage = ;
}
} cout << "正式员工:"<< strName.c_str()<< " 实际工资为:" << dbWeeklyWage << "元" << endl;
} void Visit(CPartTimeEmployee *pPartTimeEmployee)
{
string strName = pPartTimeEmployee->GetName();
double dbWeeklyWage = pPartTimeEmployee->GetWeeklyWage();
int nWeeklyTime = pPartTimeEmployee->GetWeeklyTime(); cout << "临时员工:"<< strName.c_str()<< " 实际工资为:" << dbWeeklyWage *nWeeklyTime<< "元" << endl;
}
};
1.4.6 人力资源部门类
class CHrDepartment : public IDepartment
{
public:
CHrDepartment(){}
~CHrDepartment(){} void Visit(CFullTimeEmployee *pFullTimeEmployee)
{
string strName = pFullTimeEmployee->GetName();
int nWeeklyTime = pFullTimeEmployee->GetWeeklyTime(); if (nWeeklyTime > )
{
cout << "正式员工:"<< strName.c_str()<< " 加班时间为:" << nWeeklyTime-<< "小时" << endl;
}
else if (nWeeklyTime < )
{
cout << "正式员工:"<< strName.c_str()<< " 请假时间为:" << -nWeeklyTime<< "小时" << endl;
}
} void Visit(CPartTimeEmployee *pPartTimeEmployee)
{
string strName = pPartTimeEmployee->GetName();
int nWeeklyTime = pPartTimeEmployee->GetWeeklyTime(); cout << "临时员工:"<< strName.c_str()<< " 上班时间为:" << nWeeklyTime<< "小时" << endl;
}
};
1.4.6 员工信息管理类
#pragma once #include <vector>
using namespace std; #include "employee.h"
#include "department.h" // 被访问者管理类
class CEmployeeMgr
{
public:
CEmployeeMgr(){}
~CEmployeeMgr()
{
vector<IEmployee *>::iterator iter;
for (iter = m_EmployeeVect.begin(); iter != m_EmployeeVect.end();iter ++)
{
delete *iter;
}
} void Add(IEmployee *pEmployee)
{
m_EmployeeVect.push_back(pEmployee);
} void Accept(IDepartment *pDepartment)
{
vector<IEmployee *>::iterator iter;
for (iter = m_EmployeeVect.begin(); iter != m_EmployeeVect.end();iter ++)
{
(*iter)->Accept(pDepartment);
}
} private:
vector<IEmployee *> m_EmployeeVect;
};
1.5 测试
#include "stdio.h" #include "employeemgr.h" void main()
{
CEmployeeMgr *pEmployeeMgr = new CEmployeeMgr(); IEmployee *pEmployee1 = new CFullTimeEmployee("张三", 3200.00, );
IEmployee *pEmployee2 = new CFullTimeEmployee("李四", , );
IEmployee *pEmployee3 = new CFullTimeEmployee("王麻子", , );
IEmployee *pEmployee4 = new CPartTimeEmployee("路人甲", , );
IEmployee *pEmployee5 = new CPartTimeEmployee("路人乙", , );
IEmployee *pEmployee6 = new CPartTimeEmployee("路人丙", , ); pEmployeeMgr->Add(pEmployee1);
pEmployeeMgr->Add(pEmployee2);
pEmployeeMgr->Add(pEmployee3);
pEmployeeMgr->Add(pEmployee4);
pEmployeeMgr->Add(pEmployee5);
pEmployeeMgr->Add(pEmployee6); IDepartment *pFinanceDepartment = new CFinanceDepartment();
IDepartment *pHrDepartment = new CHrDepartment(); pEmployeeMgr->Accept(pFinanceDepartment);
//pEmployeeMgr->Accept(pHrDepartment); delete pFinanceDepartment;
delete pHrDepartment;
delete pEmployeeMgr;
return;
}


二 访问者模式总结
2.1 主要优点
(1)增加新的访问操作十分方便,不痛不痒 => 符合开闭原则
(2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰 => 符合单一职责原则
2.2 主要缺点
(1)增加新的元素类很困难,需要在每一个访问者类中增加相应访问操作代码 => 违背了开闭原则
(2)元素对象有时候必须暴露一些自己的内部操作和状态,否则无法供访问者访问 => 破坏了元素的封装性
2.3 应用场景
(1)一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。=> 不同的类型可以有不同的访问操作
(2)对象结构中对象对应的类很少改变 很少改变 很少改变(重要的事情说三遍),但经常需要在此对象结构上定义新的操作。
注:本文中的图片和文字来源于原博设计模式的征途—16.访问者(Visitor)模式!
设计模式之访问者(visitor)模式的更多相关文章
- 设计模式-访问者(Visitor)模式
访问者模式是对象的行为模式.访问者模式的目的是封装施加在某种数据结构元素上的操作.一旦一些操作需要修改,接受这个操作的数据结构可以保持不变. 个人觉得访问者模式相对其他的设计模式来说稍微复杂,难理解一 ...
- [设计模式]访问者 Visitor 模式
访问者模式是对象的行为模式. 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变.
- 设计模式C++描述----22.访问者(Visitor)模式
一. 访问者模式 定义:表示一个作用于某对象结构中的各元素的操作.它你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 结构如下: 二. 举例 假设有一项科学实验,是用来对比两种种子在不同环 ...
- Java 的双重分发与 Visitor 模式
双重分发(Double Dispatch) 什么是双重分发? 谈起面向对象的程序设计时,常说起的面向对象的「多态」,其中关于多态,经常有一个说法是「父类引用指向子类对象」. 这种父类的引用指向子类对象 ...
- [设计模式] 23 访问者模式 visitor Pattern
在GOF的<设计模式:可复用面向对象软件的基础>一书中对访问者模式是这样说的:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.访问 ...
- 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)
原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabc ...
- 设计模式(17) 访问者模式(VISITOR) C++实现
意图: 表示一个作用于某对象结构的各元素的操作.它使你可以再不改变各元素的类的前提下定义作用于这些元素的新操作. 动机: 之前在学校的最后一个小项目就是做一个编译器,当时使用的就是访问者模式. 在静态 ...
- 设计模式23:Visitor 访问者模式(行为型模式)
Visitor 访问者模式(行为型模式) 动机(Motivation)在软件构造过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的修改,将会给子类带来繁重的 ...
- 设计模式:访问者(Visitor)模式
设计模式:访问者(Visitor)模式 一.前言 什么叫做访问,如果大家学过数据结构,对于这点就很清晰了,遍历就是访问的一般形式,单独读取一个元素进行相应的处理也叫作访问,读取到想要查看的内容+ ...
- Visitor模式(访问者设计模式)
Visitor ? 在Visitor模式中,数据结构与处理被分离开来.我们编写一个表示"访问者"的类来访问数据结构中的元素, 并把对各元素的处理交给访问者类.这样,当需要增加新的处 ...
随机推荐
- SqlHelper简单实现(通过Expression和反射)7.MySql数据处理类
MySql的数据处理类和Sql Server没有太大差别,从思路上来说没有区别,所以此处只是给出代码,不再多加解释了. using System; using System.Configuration ...
- 保护你的代码,生成.a文件以及.framework文件需要注意的地方
一个好的设计,一个方便使用的控件封装,一个酷炫的动画... 是不是迫不及待要分享给大家你的每一个突然蹦出来的好的idea,那就下手吧! 可是,你想要的只是让大家使用它,而不是把所有技术点都公开给每个人 ...
- seven habits of highly effective people 高效能人士的七个习惯
习惯的模型 : dependent 依赖 -- independent 独立自主 --interdependent 互相依赖 1: be proactive 主动积极 what you can ...
- iOS 那些年我们遇到的坑
1坑: UITableView的第一个Cell下移
- SElinux 读懂.te 定义自己的 .te【转】
本文转载自:https://blog.csdn.net/kongbaidepao/article/details/61417291 一. .te 文件定义中的一些宏 1.1 unix_socket_c ...
- [Android]热修复框架AndFix测试说明
AndFix,全称是Android hot-fix.是阿里开源的一个热补丁框架,允许APP在不重新发布版本的情况下修复线上的bug.支持Android 2.3 到 6.0,并且支持arm 与 X86系 ...
- CSS + Jquery
关于 position 1.父视图通过设置relative 子视图设置absolute ,这样可以相当于父视图来设置. 2.relative 不脱离文本流 位置还保留着 3.absolute 脱离 ...
- Android -- 工程架构,电话拨号器, 点击事件的4中写法
(该系列整理自张泽华android视频教程) 1. android工程 各个文件夹的作用 src/ java原代码存放目录 gen/ 自动生成目录 gen 目录中存放所有由Android开发工具自动 ...
- 2017版:KVM 性能优化之内存优化
我们说完CPU方面的优化,接着我们继续第二块内容,也就是内存方面的优化.内存方面有以下四个方向去着手: EPT 技术 大页和透明大页 KSM 技术 内存限制 1. EPT技术 EPT也就是扩展页表,这 ...
- docker定义数据卷及数据卷的备份恢复
前言:生产环境中使用docker时,往往需要对数据进行持久化(只有把容器导出为镜像,才能够保存写的数据,否则容器删除或者停止,所有数据都会没有),或者需要在多个容器之间进行数据共享,这必然涉及容器的数 ...