设计模式之访问者(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模式中,数据结构与处理被分离开来.我们编写一个表示"访问者"的类来访问数据结构中的元素, 并把对各元素的处理交给访问者类.这样,当需要增加新的处 ...
随机推荐
- 运行报警告UserWarning: Unknown extension is not supported and will be removed warn(msg)
运行python代码,出现如下警告: C:\Users\niko\PycharmProjects\python_new\venv\lib\site-packages\openpyxl\reader\w ...
- C# ---sender
在某个方法中: 第一种写法: private void btn4_Click_1(object sender, RoutedEventArgs e) { btn1_Click(null, null); ...
- Vmware ESXi 6.0 多Vlan部署,vSphere Client管理方法
背景: 公司IT部门新购了两台服务器与一台存储,打算做虚拟化,并将存储分成两个部分,分别配给那两台服务器.在宿主机上要安装的虚拟机属于不同的网段,这就涉及了多VLAN,当然这并不是多么高深的技术,属于 ...
- 20145235李涛《网络对抗》Exp2 后门原理与实践
Windows获得Linux Shell Linux获得windows shell 实验内容 使用netcat获取主机操作shell,cron启动 使用socat获取主机shell,任务计划启动 使用 ...
- Python中用format函数格式化字符串的用法(2.7版本讲解哦!)
语法 它通过{}和:来代替%.“映射”示例 通过位置 In [1]: '{0},{1}'.format('kzc',18) Out[1]: 'kzc,18' In [2]: '{},{}'.forma ...
- Spring Boot 中配置切换
步骤一:切换需求 有时候在本地测试是使用8080端口(默认端口),可是上线时使用的比如是9090端口(不常用的,以防被黑). 此时就可以通过多配置文件实现多配置支持与灵活切换. 步骤二:多配置文件 3 ...
- 关于使用UniForm以其他控件为Parent时应该注意的问题
关于使用UniForm以其他控件为Parent时应该注意的问题: 1,不能在其他组件的oncreate,onbeforeshow,onshow等事件中来生成这样的uniform,否则其上的组件不能显示 ...
- spark SQL学习(load和save操作)
load操作:主要用于加载数据,创建出DataFrame save操作:主要用于将DataFrame中的数据保存到文件中 代码示例(默认为parquet数据源类型) package wujiadong ...
- 深度学习框架TensorFlow在Kubernetes上的实践
什么是TensorFlow TensorFlow是谷歌在去年11月份开源出来的深度学习框架.开篇我们提到过AlphaGo,它的开发团队DeepMind已经宣布之后的所有系统都将基于TensorFlow ...
- 关于jQuery中的offset()和position()
在jQuery中有两个获取元素位置的方法offset()和position().position()方法是在1.2.6版本之后加入的,为什么要引 入这个方法呢?这两个方法之间有什么异同?使用的时候应该 ...