33.2 门面模式VS中介者模式

33.2 门面模式VS中介者模式

门面模式为复杂的子系统提供一个统一的访问界面,它定义的是一个高层接口,该接口使得子系统更加容易使用,避免外部模块深入到子系统内部而产生与子系统内部细节耦合的问题。中介者模式使用一个中介对象来封装一系列同事对象的交互行为,它使各对象之间不再显式地引用,从而使其耦合松散,建立一个可扩展的应用架构。

33.2.1 中介者模式实现工资计算

大家工作会得到工资,那么工资与哪些因素有关呢?这里假设工资与职位、税收有关, 职位提升工资就会增加,同时税收也增加,职位下降了工资也同步降低,当然税收也降低。 而如果税收比率增加了呢?工资自然就减少了!这三者之间两两都有关系,很适合中介者模式的场景,类图如图33-4所示。

image-20210930203153669

图33-4 工资、职位、税收的示意类图

类图中的方法比较简单,我们主要分析的是三者之间的关系,通过类图可以发现三者之间已经没有耦合,原本在需求分析时我们发现三者有直接的交互,采用中介者模式后,三个对象之间已经相互独立了,全部委托中介者完成。我们在类图中还定义了一个抽象同事类, 它是一个标志性接口,其子类都是同事类,都可以被中介者接收,如代码清单33-11所示。

代码清单33-11 抽象同事类

1
2
3
4
5
6
7
public abstract class AbsColleague {
//每个同事类都对中介者非常了解
protected AbsMediator mediator;
public AbsColleague(AbsMediator _mediator){
this.mediator = _mediator;
}
}

在抽象同事类中定义了每个同事类对中介者都非常了解,如此才能把请求委托给中介者完成。三个同事类都具有相同的设计,即定义一个业务接口以及每个对象必须实现的职责, 同时既然是同事类就都继承AbsColleague。抽象同事类只是一个标志性父类,并没有限制子类的业务逻辑,因此每一个同事类并没有违背单一职责原则。首先来看职位接口,如代码清单33-12所示。

代码清单33-12 职位接口

1
2
3
4
5
6
public interface IPosition {
//升职
public void promote();
//降职
public void demote();
}

职位会有升有降,职位变化如代码清单33-13所示。

代码清单33-13 职位

1
2
3
4
5
6
7
8
9
10
11
public class Position extends AbsColleague implements IPosition {
public Position(AbsMediator _mediator){
super(_mediator);
}
public void demote() {
super.mediator.down(this);
}
public void promote() {
super.mediator.up(this);
}
}

每一个职位的升降动作都委托给中介者执行,具体一个职位升降影响到谁这里没有定义,完全由中介者完成,简单而且扩展性非常好。下面我们来看工资接口,如代码清单33- 14所示。

代码清单33-14 工资接口

1
2
3
4
5
6
public interface ISalary {
//加薪
public void increaseSalary();
//降薪
public void decreaseSalary();
}

工资也会有升有降,如代码清单33-15所示。

代码清单33-15 工资

1
2
3
4
5
6
7
8
9
10
11
public class Salary extends AbsColleague implements ISalary {
public Salary(AbsMediator _mediator){
super(_mediator);
}
public void decreaseSalary() {
super.mediator.down(this);
}
public void increaseSalary() {
super.mediator.up(this);
}
}

交税是公民的义务,税收接口如代码清单33-16所示。

代码清单33-16 税收接口

1
2
3
4
5
6
public interface ITax {
//税收上升
public void raise();
//税收下降
public void drop();
}

税收的变化对我们的工资当然有影响,如代码清单33-17所示。

代码清单33-17 税收

1
2
3
4
5
6
7
8
9
10
11
12
public class Tax extends AbsColleague implements ITax {
//注入中介者
public Tax(AbsMediator _mediator){
super(_mediator);
}
public void drop() {
super.mediator.down(this);
}
public void raise() {
super.mediator.up(this);
}
}

以上同事类的业务都委托给了中介者,其本类已经没有任何的逻辑了,非常简单,现在的问题是中介者类非常复杂,因为它要处理三者之间的关系。我们首先来看抽象中介者,如代码清单33-18所示。

代码清单33-18 抽象中介者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public abstract class AbsMediator {
//工资
protected final ISalary salary;
//职位
protected final IPosition position;
//税收
protected final ITax tax;
public AbsMediator(){
salary = new Salary(this);
position = new Position(this);
tax = new Tax(this);
}
//工资增加了
public abstract void up(ISalary _salary);
//职位提升了
public abstract void up(IPosition _position);
//税收增加了
public abstract void up(ITax _tax);
//工资降低了
public abstract void down(ISalary _salary);
//职位降低了
public abstract void down(IPosition _position);
//税收降低了
public abstract void down(ITax _tax);
}

在抽象中介者中我们定义了6个方法,分别处理职位升降、工资升降以及税收升降的业务逻辑,采用Java多态机制来实现,我们来看实现类,如代码清单33-19所示。

代码清单33-19 中介者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Mediator extends AbsMediator{
//工资增加了
public void up(ISalary _salary) {
upSalary();
upTax();
}
//职位提升了
public void up(IPosition position) {
upPosition();
upSalary();
upTax();

}
//税收增加了
public void up(ITax tax) {
upTax();
downSalary();
}
/**工资、职位、税收降低的处理方法相同,不再赘述 *///工资增加
private void upSalary(){
System.out.println("工资翻倍,乐翻天");
}
private void upTax(){
System.out.println("税收上升,为国家做贡献");
}
private void upPosition(){
System.out.println("职位上升一级,狂喜");
}
private void downSalary(){
System.out.println("经济不景气,降低工资");
}
private void downTax(){
System.out.println("税收减低,国家收入减少");
}
private void downPostion(){
System.out.println("官降三级,比自杀还痛苦");
}
}

该类的方法较多,但是还是非常简单的,它的12个方法分为两大类型:一类是每个业务的独立流程,比如增加工资,仅仅实现单独增加工资的职能,而不关心职位、税收是如何变化的,该类型的方法是private私有类型,只能提供本类内访问;另一类是实现抽象中介者定义的方法,完成具体的每一个逻辑,比如职位上升,同时也引起了工资增加、税收增加。我们编写一个场景类,看看运行结果,如代码清单33-20所示。

代码清单33-20 场景类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
//定义中介者
Mediator mediator = new Mediator();
//定义各个同事类
IPosition position = new Position(mediator);
ISalary salary = new Salary(mediator);
ITax tax = new Tax(mediator);
//职位提升了
System.out.println("===职位提升===");
position.promote();
}
}

运行结果如下所示:

1
2
3
4
===职位提升=== 
职位上升一级,狂喜
工资翻倍,乐翻天
税收上升,为国家做贡献

我们回过头来分析一下设计,在接收到需求后我们发现职位、工资、税收之间有着紧密的耦合关系,如果不采用中介者模式,则每个对象都要与其他两个对象进行通信,这势必会增加系统的复杂性,同时也使系统处于僵化状态,很难实现拥抱变化的理想。通过增加一个中介者,每个同事类的职位、工资、税收都只与中介者通信,中介者封装了各个同事类之间的逻辑关系,方便系统的扩展和维护。

33.2.2 门面模式实现工资计算

工资计算是一件非常复杂的事情,简单来说,它是对基本工资、月奖金、岗位津贴、绩效、考勤、税收、福利等因素综合运算后的一个数字。即使设计一个HR(人力资源)系统,员工工资计算也是非常复杂的模块,但是对于外界,比如高管层,最希望看到的结果是张三拿了多少钱,李四拿了多少钱,而不是看中间的计算过程,怎么计算那是人事部门的事情。换句话说,对外界的访问者来说,它只要传递进去一个人员名称和月份即可获得工资数,而不用关心其中的计算有多么复杂,这就用得上门面模式了。

门面模式对子系统起封装作用,它可以提供一个统一的对外服务接口,如图33-5所示。

image-20210930203813115

图33-5 HR系统的类图

该类图主要实现了工资计算,通过HRFacade门面可以查询用户的工资以及出勤天数等,而不用关心这个工资或者出勤天数是怎么计算出来的,从而屏蔽了外系统对工资计算模块的内部细节依赖。我们先看子系统内部的各个实现,考勤情况如代码清单33-21所示。

代码清单33-21 考勤情况

1
2
3
4
5
6
public class Attendance {
//得到出勤天数
public int getWorkDays(){
return (new Random()).nextInt(30);
}
}

非常简单,只用一个方法获得一个员工的出勤天数。我们再来看奖金计算,如代码清单33-22所示。

代码清单33-22 奖金计算

1
2
3
4
5
6
7
8
9
10
11
12
public class Bonus {
//考勤情况
private Attendance atte = new Attendance();
//奖金
public int getBonus(){
//获得出勤情况
int workDays = atte.getWorkDays();
//奖金计算模型
int bonus = workDays * 1800 / 30;
return bonus;
}
}

我们在这里实现了一个示意方法,实际的奖金计算是非常复杂的,与考勤、绩效、基本工资、岗位都有关系,单单一个奖金计算就可以设计出一个门面。我们再来看基本工资,这个基本上是按照职位而定的,比较固定,如代码清单33-23所示。

代码清单33-23 基本工资

1
2
3
4
5
6
public class BasicSalary {
//获得一个人的基本工资
public int getBasicSalary(){
return 2000;
}
}

我们定义了员工的基本工资都为2000元,没有任何浮动的余地。再来看绩效,如代码清单33-24所示。

代码清单33-24 绩效

1
2
3
4
5
6
7
8
9
10
public class Performance {
//基本工资
private BasicSalary salary = new BasicSalary();
//绩效奖励
public int getPerformanceValue(){
//随机绩效
int perf = (new Random()).nextInt(100);
return salary.getBasicSalary() * perf /100;
}
}

绩效按照一个非常简单的算法,即基本工资乘以一个随机的百分比。我们再来看税收,如代码清单33-25所示。

代码清单33-25 税收

1
2
3
4
5
6
7
public class Tax {
//收取多少税金
public int getTax(){
//交纳一个随机数量的税金
return (new Random()).nextInt(300);
}
}

一个计算员工薪酬的所有子元素都已经具备了,剩下的就是编写组合逻辑类,总工资的计算如代码清单33-26所示。

代码清单33-26 总工资计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SalaryProvider {
//基本工资
private BasicSalary basicSalary = new BasicSalary();
//奖金
private Bonus bonus = new Bonus();
//绩效
private Performance perf = new Performance();
//税收
private Tax tax = new Tax();
//获得用户的总收入
public int totalSalary(){
return basicSalary.getBasicSalary() + bonus.getBonus() + perf.getPerformanceValue() - tax.getTax();
}
}

这里只是对前面的元素值做了一个加减法计算,这是对实际HR系统的简化处理,如果把这个类暴露给外系统,那么被修改的风险是非常大的,因为它的方法totalSalary是一个具体的业务逻辑。我们采用门面模式的目的是要求门面是无逻辑的,与业务无关,只是一个子系统的访问入口。门面模式只是一个技术层次上的实现,全部业务还是在子系统内实现。我们来看HR门面,如代码清单33-27所示。

代码清单33-27 HR门面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HRFacade {
//总工资情况
private SalaryProvider salaryProvider = new SalaryProvider();
//考勤情况
private Attendance attendance = new Attendance();
//查询一个人的总收入
public int querySalary(String name,Date date){
return salaryProvider.totalSalary();
}
//查询一个员工一个月工作了多少天
public int queryWorkDays(String name){
return attendance.getWorkDays();
}
}

所有的行为都是委托行为,由具体的子系统实现,门面只是提供了一个统一访问的基础而已,不做任何的校验、判断、异常等处理。我们编写一个场景类查看运行结果,如代码清单33-28所示。

代码清单33-28 场景类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
//定义门面
HRFacade facade = new HRFacade();
System.out.println("===外系统查询总收入===");
int salary = facade.querySalary("张三",new Date(System. currentTimeMillis()));
System.out.println( "张三 11月 总收入为:" +salary);
//再查询出勤天数
System.out.println("\n===外系统查询出勤天数===");
int workDays = facade.queryWorkDays("李四");
System.out.println("李四 本月出勤:" +workDays);
}
}

运行结果如下所示:

1
2
3
4
===外系统查询总收入=== 
张三 11月 总收入为:4133
===外系统查询出勤天数===
李四 本月出勤:22

在该例中,我们使用了门面模式对薪水计算子系统进行封装,避免子系统内部复杂逻辑外泄,确保子系统的业务逻辑的单纯性,即使业务流程需要变更,影响的也是子系统内部功能,比如奖金需要与基本工资挂钩,这样的修改对外系统来说是透明的,只需要子系统内部变更即可。

33.2.3 最佳实践

门面模式和中介者模式之间的区别还是比较明显的,门面模式是以封装和隔离为主要任务,而中介者模式则是以调和同事类之间的关系为主,因为要调和,所以具有了部分的业务逻辑控制。两者的主要区别如下:

  • 功能区别

门面模式只是增加了一个门面,它对子系统来说没有增加任何的功能,子系统若脱离门 面模式是完全可以独立运行的。而中介者模式则增加了业务功能,它把各个同事类中的原有 耦合关系移植到了中介者,同事类不可能脱离中介者而独立存在,除非是想增加系统的复杂 性和降低扩展性。

  • 知晓状态不同

对门面模式来说,子系统不知道有门面存在,而对中介者来说,每个同事类都知道中介 者存在,因为要依靠中介者调和同事之间的关系,它们对中介者非常了解。

  • 封装程度不同

门面模式是一种简单的封装,所有的请求处理都委托给子系统完成,而中介者模式则需要有一个中心,由中心协调同事类完成,并且中心本身也完成部分业务,它属于更进一步的业务功能封装。