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

在软件开发中,有时候也需要处理像处方单这样的集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式。在设计模式中,有一种模式可以满足上述要求,其模式动机就是以不同的方式操作复杂对象结构,该模式就是访问者模式。

  

  访问者模式是一个可以考虑用来解决的方案,它可以在一定程度上解决上述问题(大部分问题)。

一 访问者模式概述

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)模式的更多相关文章

  1. 设计模式-访问者(Visitor)模式

    访问者模式是对象的行为模式.访问者模式的目的是封装施加在某种数据结构元素上的操作.一旦一些操作需要修改,接受这个操作的数据结构可以保持不变. 个人觉得访问者模式相对其他的设计模式来说稍微复杂,难理解一 ...

  2. [设计模式]访问者 Visitor 模式

    访问者模式是对象的行为模式. 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变.

  3. 设计模式C++描述----22.访问者(Visitor)模式

    一. 访问者模式 定义:表示一个作用于某对象结构中的各元素的操作.它你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 结构如下: 二. 举例 假设有一项科学实验,是用来对比两种种子在不同环 ...

  4. Java 的双重分发与 Visitor 模式

    双重分发(Double Dispatch) 什么是双重分发? 谈起面向对象的程序设计时,常说起的面向对象的「多态」,其中关于多态,经常有一个说法是「父类引用指向子类对象」. 这种父类的引用指向子类对象 ...

  5. [设计模式] 23 访问者模式 visitor Pattern

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对访问者模式是这样说的:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.访问 ...

  6. 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)

    原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabc ...

  7. 设计模式(17) 访问者模式(VISITOR) C++实现

    意图: 表示一个作用于某对象结构的各元素的操作.它使你可以再不改变各元素的类的前提下定义作用于这些元素的新操作. 动机: 之前在学校的最后一个小项目就是做一个编译器,当时使用的就是访问者模式. 在静态 ...

  8. 设计模式23:Visitor 访问者模式(行为型模式)

    Visitor 访问者模式(行为型模式) 动机(Motivation)在软件构造过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的修改,将会给子类带来繁重的 ...

  9. 设计模式:访问者(Visitor)模式

    设计模式:访问者(Visitor)模式 一.前言    什么叫做访问,如果大家学过数据结构,对于这点就很清晰了,遍历就是访问的一般形式,单独读取一个元素进行相应的处理也叫作访问,读取到想要查看的内容+ ...

  10. Visitor模式(访问者设计模式)

    Visitor ? 在Visitor模式中,数据结构与处理被分离开来.我们编写一个表示"访问者"的类来访问数据结构中的元素, 并把对各元素的处理交给访问者类.这样,当需要增加新的处 ...

随机推荐

  1. libvirt-qemu-虚拟机设备热插拔

    cpu热插拔 # virsh setvcpus $domain_name --count 4 --live (--config可写入配置文件永久保存) #前提条件和后续激活参考<libvirt- ...

  2. 【Java】仿真qq尝试:用户注册(一)

    需求: 1.流程分析:客户端程序拿到用户名和密码,将用户名和密码发送到服务端(在客户端验证合法性),服务端接收并存储用户名和密码,返回给客户端一个信息(可能是成功也可能是失败.) 2.数据怎么存?服务 ...

  3. jQuery双向滑动杆 设置数值百分比

    在线演示 本地下载

  4. offset,client,scroll,style,getBoundingClientRect相关笔记

    1.offsetTop 功能:获取元素上外缘与最近的定位父元素内壁的距离,如果没有定位父元素,则是与文档上内壁的距离 使用方法:js document.querySelector(...).offse ...

  5. 新机git及github sshkey简单配置

    新机git简单配置,毕竟不常用,不用每次都查1.安装gitwindows:https://git-scm.com/download/winubuntu: apt install git 2.全局配置 ...

  6. android timed gpio (linux 3.0.0) 受时钟控制的gpio【转】

    本文转载自:https://blog.csdn.net/linxi_hnh/article/details/8043417 1 路径: drivers/staging/android/timed_gp ...

  7. Linux上超酷的命令行扩展工具Oh My Zsh

    Oh My Zsh 是一款社区驱动的命令行工具,正如它的主页上说的,Oh My Zsh 是一种生活方式.它基于 zsh 命令行,提供了主题配置,插件机制,已经内置的便捷操作.给我们一种全新的方式使用命 ...

  8. Mybatis中接口和对应的mapper文件位置配置深入剖析

    首先要说明的问题是,Mybatis中接口和对应的mapper文件不一定要放在同一个包下,放在一起的目的是为了Mybatis进行自动扫描,并且要注意此时java接口的名称和mapper文件的名称要相同, ...

  9. LeetCode——Max Consecutive Ones

    LeetCode--Max Consecutive Ones Question Given a binary array, find the maximum number of consecutive ...

  10. nginx+tomcat网页动静分离配置

    1.环境描述 nginx server (Proxy):192.168.1.135(作为代理服务器)WEB server1: 192.168.1.138(使用tomcat作为web容器)WEB ser ...