设计模式之访问者(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) = 0; };
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) = 0; virtual void Visit(CPartTimeEmployee *pPartTimeEmployee) = 0; };
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 >= 40) { dbWeeklyWage = dbWeeklyWage + (nWeeklyTime - 40) * 50; } else { dbWeeklyWage = dbWeeklyWage - (40 - nWeeklyTime) * 80; if (dbWeeklyWage < 0) { dbWeeklyWage = 0; } } 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 > 40) { cout << "正式员工:"<< strName.c_str()<< " 加班时间为:" << nWeeklyTime-40<< "小时" << endl; } else if (nWeeklyTime < 40) { cout << "正式员工:"<< strName.c_str()<< " 请假时间为:" << 40-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, 45); IEmployee *pEmployee2 = new CFullTimeEmployee("李四", 2000, 40); IEmployee *pEmployee3 = new CFullTimeEmployee("王麻子", 2400, 38); IEmployee *pEmployee4 = new CPartTimeEmployee("路人甲", 80, 20); IEmployee *pEmployee5 = new CPartTimeEmployee("路人乙", 60, 18); IEmployee *pEmployee6 = new CPartTimeEmployee("路人丙", 70, 19); 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)模式!