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

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

  

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

一 访问者模式概述

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. MongoDB win32-bit 安装

    一念起: 由于本人 用的电脑比较老旧,所以一直用的 是win7 32bit 的操作系统,但是在学习MongoDB的时候 遇到了起步的第一个问题,按照目前 官网最新版MongoDB 3.4.3,已不支持 ...

  2. oracle 任务使用

    文章访问地址:http://www.cnblogs.com/hoojo/p/oracle_procedure_job_interval.html

  3. 认识shiro

    shiro是安全(权限)框架,不仅可以在javase中也可以在javaee中 shiro可以完成认证.授权.加密.会话管理,与web进行集成.缓存等. Authentication:身份认证/登录,验 ...

  4. FTP pure-ftpd 安装、管理

    FTP简介 FTP是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为文传协议,用户Internet上的控制文件的双向传输. FTP的主要作用,就是让用户链接上一个远 ...

  5. SQL-ALTER-change和modify区别

      ALTER 对于列的应用:   1.更改列名      格式:CHANGE old_col_name new_col_name column_definition      保留old和new列名 ...

  6. 出入Spring boot(六)数据访问

    Spring Data提供了使用统一的API进行数据访问操作,这是Spring通过提供Spring DataCommons项目来实现的,它是Spring data的依赖Spring Data Comm ...

  7. ES6 Babel 简单使用

    ECMAScript 是 JS 的语言标准.而 ES6 是新的 JS 语法标准. PS:严格来说,ECMAScript 还包括其他很多语言的语言标准. ECMAScript 发展历史 1995年:EC ...

  8. 回溯和DFS效率分析

    回溯和DFS效率分析 一.心得 多组数据记得初始化 两组样例,找圆点点的个数 6 9 ....#. .....# ...... ...... ...... ...... ...... #@...# . ...

  9. python学习笔记(接口自动化框架 V1.0)

    之前是利用python自带的unittest测试框架 这次自己设计一个 之后再一点点往里面加功能 (ps:当然这个框架真的是很简单..很简单...很简单...) excel文件格式: #!/usr/b ...

  10. 【C#基本功 控件的用法】 委托

    接触C#这段时间,很多内容容易理解,但也也有很多内容总是无法踏过门槛,就像Delegate 委托,这种新的机制是Labview不具备的,他的一个用法,也让我们这些从labview跨越过来的coder, ...