1、概述
门面模式为复杂的子系统提供一个统一的访问界面,它定义的是一个高层接口,该接口使得子系统更加容易使用,避免外部模块深入到子系统内部而产生与子系统内部细节耦合的问题。中介者模式使用一个中介对象来封装一系列同事对象的交互行为,它使各对象之间不再显式地引用,从而使其耦合松散,建立一个可扩展的应用架构。
2、中介者模式实现工资计算
2.1 类图
大家工作会得到工资,那么工资与哪些因素有关呢?这里假设工资与职位、税收有关,职位提升工资就会增加,同时税收也增加,职位下降了工资也同步降低,当然税收也降低。而如果税收比率增加了呢?工资自然就减少了!这三者之间两两都有关系,很适合中介者模式的场景。
类图中的方法比较简单,我们主要分析的是三者之间的关系,通过类图可以发现三者之间已经没有耦合,原本在需求分析时我们发现三者有直接的交互,采用中介者模式后,三个对象之间已经相互独立了,全部委托中介者完成。我们在类图中还定义了一个抽象同事类,它是一个标志性接口,其子类都是同事类,都可以被中介者接收。
2.2 代码
2.2.1 抽象同事类
// CAbsColleague.h class CAbsColleague { public: CAbsColleague(CAbsMediator *opMediator); ~CAbsColleague(); protected: //每个同事类都对中介者非常了解 CAbsMediator *mopMediator; }; // CAbsColleague.cpp CAbsColleague::CAbsColleague(CAbsMediator *opMediator) :mopMediator(opMediator){} CAbsColleague::~CAbsColleague(){}
抽象同事类中定义了每个同事类对中介者都非常了解,如此才能把请求委托给中介者完成。三个同事类都具有相同的设计,即定义一个业务接口以及每个对象必须实现的职责,同时既然是同事类就都继承CAbsColleague。抽象同事类只是一个标志性父类,并没有限制子类的业务逻辑,因此每一个同事类并没有违背单一职责原则。
2.2.2 职位
// CIPosition.h class CIPosition { public: CIPosition(); ~CIPosition(); virtual void mvPromote() = 0; virtual void mvDemote() = 0; }; // CIPosition.cpp CIPosition::CIPosition(){}; CIPosition::~CIPosition(){};
职位会有升有降。
// CPosition.h class CPosition : public CAbsColleague, public CIPosition { public: CPosition(CAbsMediator *opMediator); ~CPosition(); void mvPromote(); void mvDemote(); }; // CPosition.cpp CPosition::CPosition(CAbsMediator *opMediator) : CAbsColleague(opMediator) { mopMediator->mvSetPosition(this); } CPosition::~CPosition(){} void CPosition::mvPromote() { mopMediator->mvUp(this); } void CPosition::mvDemote() { mopMediator->mvDown(this); }
每一个职位的升降动作都委托给中介者执行,具体一个职位升降影响到谁这里没有定义,完全由中介者完成,简单而且扩展性非常好。
2.2.3 工资
// CISalary.h class CISalary { public: CISalary(); ~CISalary(); //加薪 virtual void mvIncreaseSalary() = 0; //降薪 virtual void mvDecreaseSalary() = 0; }; // CISalary.cpp CISalary::CISalary(){} CISalary::~CISalary(){}
工资也会有升有降。
// CSalary.h class CSalary :public CAbsColleague, public CISalary { public: CSalary(CAbsMediator *opMediator); ~CSalary(); void mvIncreaseSalary(); void mvDecreaseSalary(); }; // CSalary.cpp CSalary::CSalary(CAbsMediator *opMediator) : CAbsColleague(opMediator) { mopMediator->mvSetSalary(this); } CSalary::~CSalary(){} void CSalary::mvIncreaseSalary() { mopMediator->mvUp(this); } void CSalary::mvDecreaseSalary() { mopMediator->mvDown(this); }
2.2.4 税收
// CITax.h class CITax { public: CITax(); ~CITax(); // 税收上升 virtual void mvRaise() = 0; // 税收下降 virtual void mvDrop() = 0; }; // CITax.cpp CITax::CITax(){} CITax::~CITax(){}
税收同样有升有降。
//CTax.h class CTax : public CAbsColleague, public CITax { public: CTax(CAbsMediator *opMediator); ~CTax(); void mvRaise(); void mvDrop(); }; // CTax.cpp CTax::CTax(CAbsMediator *opMediator) : CAbsColleague(opMediator) { mopMediator->mvSetTax(this); } CTax::~CTax(){} void CTax::mvRaise() { mopMediator->mvUp(this); } void CTax::mvDrop() { mopMediator->mvDown(this); }
2.2.5 中介类
以上同事类的业务都委托给了中介者,其本类已经没有任何的逻辑了,非常简单,现在的问题是中介者类非常复杂,因为它要处理三者之间的关系。
// CAbsMediator.h class CAbsMediator { public: CAbsMediator(); ~CAbsMediator(); void mvSetSalary(CISalary *opSalary); void mvSetPosition(CIPosition *opPosition); void mvSetTax(CITax *opTax); //工资增加了 virtual void mvUp(CISalary *opSalary) = 0; //职位提升了 virtual void mvUp(CIPosition *opPosition) = 0; //税收增加了 virtual void mvUp(CITax *opTax) = 0; //工资降低了 virtual void mvDown(CISalary *opSalary) = 0; //职位降低了 virtual void mvDown(CIPosition *opPosition) = 0; //税收降低了 virtual void mvDown(CITax *opTax) = 0; protected: //工资 CISalary *mopSalary; //职位 CIPosition *mopPosition; //税收 CITax *mopTax; }; // CAbsMediator.cpp CAbsMediator::CAbsMediator(){} CAbsMediator::~CAbsMediator(){} void CAbsMediator::mvSetSalary(CISalary *opSalary) { mopSalary = opSalary; } void CAbsMediator::mvSetPosition(CIPosition *opPosition) { mopPosition = opPosition; } void CAbsMediator::mvSetTax(CITax *opTax) { mopTax = opTax; }
在抽象中介者中我们定义了6个方法,分别处理职位升降、工资升降以及税收升降的业务逻辑,采用多态机制来实现。我们来看具体的实现类。
// CMediator.h class CMediator : public CAbsMediator { public: CMediator(); ~CMediator(); //工资增加了 void mvUp(CISalary *opSalary); //职位提升了 void mvUp(CIPosition *opPosition); //税收增加了 void mvUp(CITax *opTax); //工资降低了 void mvDown(CISalary *opSalary); //职位降低了 void mvDown(CIPosition *opPosition); //税收降低了 void mvDown(CITax *opTax); }; // CMediator.cpp CMediator::CMediator(){} CMediator::~CMediator(){} // 工资增加了 void CMediator::mvUp(CISalary *opSalary) { cout << "增加税收" << endl; cout << "增加工资" << endl; } //职位提升了 void CMediator::mvUp(CIPosition *opPosition) { cout << "增加税收." << endl; cout << "增加工资." << endl; cout << "提升职位." << endl; } //税收增加了 void CMediator::mvUp(CITax *opTax) { cout << "增加税收." << endl; cout << "增加工资." << endl; } //工资降低了 void CMediator::mvDown(CISalary *opSalary) { cout << "减少税收. " << endl; cout << "降低工资." << endl; } //职位降低了 void CMediator::mvDown(CIPosition *opPosition) { cout << "减少税收. " << endl; cout << "降低工资." << endl; cout << "降低职位." << endl; } //税收降低了 void CMediator::mvDown(CITax *opTax) { cout << "减少税收. " << endl; cout << "降低工资." << endl; }
该类的方法较多,但是还是非常简单的,它的方法分为两大类型:一类是每个业务的独立流程,比如增加工资,仅仅实现单独增加工资的职能,而不关心职位、税收是如何变化的,该类型的方法是private私有类型,只能提供本类内访问,这里为了方便使用打印来替代调用的处理方法;另一类是实现抽象中介者定义的方法,完成具体的每一个逻辑,比如职位上升,同时也引起了工资增加、税收增加。
2.2.6 场景调用
int main() { //定义中介者 CAbsMediator *op_mediator = new CMediator; //定义各个同事类 CIPosition *op_position = new CPosition(op_mediator); CISalary *op_salary = new CSalary(op_mediator); CITax *op_tax = new CTax(op_mediator); //职位提升了 cout << "===职位提升===" << endl; op_position->mvPromote(); return 0; }
2.2.7 执行结果
2.3 小结
我们回过头来分析一下设计,在接收到需求后我们发现职位、工资、税收之间有着紧密的耦合关系,如果不采用中介者模式,则每个对象都要与其他两个对象进行通信,这势必会增加系统的复杂性,同时也使系统处于僵化状态,很难实现拥抱变化的理想。通过增加一个中介者,每个同事类的职位、工资、税收都只与中介者通信,中介者封装了各个同事类之间的逻辑关系,方便系统的扩展和维护。
3、门面模式实现工资计算
3.1 类图
工资计算是一件非常复杂的事情,简单来说,它是对基本工资、月奖金、岗位津贴、绩效、考勤、税收、福利等因素综合运算后的一个数字。即使设计一个HR(人力资源)系统,员工工资计算也是非常复杂的模块,但是对于外界,比如高管层,最希望看到的结果是张三拿了多少钱,李四拿了多少钱,而不是看中间的计算过程,怎么计算那是人事部门的事情。换句话说,对外界的访问者来说,它只要传递进去一个人员名称和月份即可获得工资数,而不用关心其中的计算有多么复杂,这就用得上门面模式了。
门面模式对子系统起封装作用,它可以提供一个统一的对外服务接口。
该类图主要实现了工资计算,通过HRFacade门面可以查询用户的工资以及出勤天数等,而不用关心这个工资或者出勤天数是怎么计算出来的,从而屏蔽了外系统对工资计算模块的内部细节依赖。
3.2 代码
我们先看子系统内部的各个实现。
3.2.1 考勤情况
非常简单, 只用一个方法获得一个员工的出勤天数。
class CAttendance { public: CAttendance(){} ~CAttendance(){} //得到出勤天数 int miGetWorkDays() { return rand() % 30; } };
3.2.2 奖金计算
我们在这里实现了一个示意方法,实际的奖金计算是非常复杂的,与考勤、绩效、基本工资、岗位都有关系,单单一个奖金计算就可以设计出一个门面。
class CBonus { public: CBonus() { mopAttendance = new CAttendance; } //奖金 int miGetBonus() { //获得出勤情况 int i_work = mopAttendance->miGetWorkDays(); //奖金计算模型 int i_bonus = i_work * 1800 / 30; return i_bonus; } private: //考勤情况 CAttendance *mopAttendance; };
3.2.3 基本工资
我们再来看基本工资,这个基本上是按照职位而定的,比较固定。我们定义了员工的基本工资都为2000元, 没有任何浮动的余地。
class CBasicSalary { public: CBasicSalary(){} ~CBasicSalary(){} //获得一个人的基本工资 int miGetBasicSalary() { return 2000; } };
3.2.4 绩效
绩效按照一个非常简单的算法,即基本工资乘以一个随机的百分比。
class CPerformance { public: CPerformance(){ mopBasicSalary = new CBasicSalary; } ~CPerformance(){} //绩效奖励 int miGetPerformance() { //随机绩效 int i_perf = rand() % 100; return mopBasicSalary->miGetBasicSalary()*i_perf / 100; } private: CBasicSalary *mopBasicSalary; };
3.2.5 税收
class CTax { public: CTax(){} ~CTax(){} //收取多少税金 int miGetTax() { //交纳一个随机数量的税金 return rand() % 300; } };
3.2.6 薪酬
一个计算员工薪酬的所有子元素都已经具备了,剩下的就是编写组合逻辑类,总工资的计算。
这里只是对前面的元素值做了一个加减法计算,这是对实际HR系统的简化处理,如果把这个类暴露给外系统,那么被修改的风险是非常大的,因为它的方法miGetTotalSalary是一个具体的业务逻辑。我们采用门面模式的目的是要求门面是无逻辑的,与业务无关,只是一个子系统的访问入口。门面模式只是一个技术层次上的实现, 全部业务还是在子系统内实现。
class CSalaryProvider { public: CSalaryProvider() { mopBasicSalary = new CBasicSalary; mopBonus = new CBonus; mopPerf = new CPerformance; mopTax = new CTax; } ~CSalaryProvider(){} int miGetTotalSalary() { return mopBasicSalary->miGetBasicSalary() + mopBonus->miGetBonus() + mopPerf->miGetPerformance() - mopTax->miGetTax(); } private: //基本工资 CBasicSalary *mopBasicSalary; //奖金 CBonus *mopBonus; //绩效 CPerformance *mopPerf; //税收 CTax *mopTax; };
3.2.7 HR门面
所有的行为都是委托行为,由具体的子系统实现,门面只是提供了一个统一访问的基础而已,不做任何的校验、判断、异常等处理。
class CHRFacade { public: CHRFacade() { mopSalary = new CSalaryProvider; mopAttendance = new CAttendance; } ~CHRFacade(){} //查询一个人的总收入 int miGetSalary(const string &sName, int iWork) { return mopSalary->miGetTotalSalary(); } //查询一个员工一个月工作了多少天 int miGetWorkDays(const string &sName) { return mopAttendance->miGetWorkDays(); } private: //总工资情况 CSalaryProvider *mopSalary; //考勤情况 CAttendance *mopAttendance; };
3.2.8 场景调用
int main() { //定义门面 CHRFacade *op_facede = new CHRFacade; //查询工资 cout << "===外系统查询总收入===" << endl; int i_salary = op_facede->miGetSalary("张三", rand()%30); cout << "张三 11月 总收入为: " << i_salary << endl; //再查询出勤天数 cout << "===外系统查询出勤天数===" << endl; int i_work_day = op_facede->miGetWorkDays("李四"); cout << "李四 本月出勤: " << i_work_day << endl; return 0; }
3.2.9 运行结果
3.3 小结
在该例中,我们使用了门面模式对薪水计算子系统进行封装,避免子系统内部复杂逻辑外泄,确保子系统的业务逻辑的单纯性,即使业务流程需要变更,影响的也是子系统内部功能,比如奖金需要与基本工资挂钩,这样的修改对外系统来说是透明的,只需要子系统内部变更即可。
4、总结
门面模式和中介者模式之间的区别还是比较明显的,门面模式是以封装和隔离为主要任务,而中介者模式则是以调和同事类之间的关系为主,因为要调和,所以具有了部分的业务逻辑控制。两者的主要区别如下:
● 功能区别
门面模式只是增加了一个门面, 它对子系统来说没有增加任何的功能, 子系统若脱离门面模式是完全可以独立运行的。而中介者模式则增加了业务功能,它把各个同事类中的原有耦合关系移植到了中介者,同事类不可能脱离中介者而独立存在,除非是想增加系统的复杂性和降低扩展性。
● 知晓状态不同
对门面模式来说,子系统不知道有门面存在,而对中介者来说,每个同事类都知道中介者存在,因为要依靠中介者调和同事之间的关系,它们对中介者非常了解。
● 封装程度不同
门面模式是一种简单的封装,所有的请求处理都委托给子系统完成,而中介者模式则需要有一个中心,由中心协调同事类完成,并且中心本身也完成部分业务,它属于更进一步的业务功能封装。