门面模式为复杂的子系统提供一个统一的访问界面,他定义的是一个高层接口,该接口使得子系统更加容易使用,避免外部模块深入到子系统内部而产生与子系统内部细节耦合的问题。中介者模式使用一个中介对象来封装一系列同时对象的交互行为,他使各对象之间不再显式的引用,从而使其耦合松散,建立一个可扩展的应用架构。
中介者模式实现工资计算
大家工作会得到工资,那么工资与哪些因素有关呢?这里假设工资与职位、税收有关,职位提升工资就会增加,同时税收也增加,职位下降了工资也同步降低,当然税收也降低。而如果税收比率增加了呢?工资自然就减少了!这三者之间两两都有关系,很适合中介者模式的场景。
我们主要分析的是三者之间的关系,通过类图可以发现三者之间已经没有耦合,原本在需求分析时我们发现三者有直接的交互,采用中介者模式后,三个对象之间已经相互独立了,全部委托中介者完成。我们定义了一个抽象同事类,他是一个标志性接口,其子类都是同事类,都可以被中介者接收,如下所示。
public abstract class AbsColleague {
// 每个同事类都对中介者非常了解
protected AbsMediator mediator;
public AbsColleague(AbsMediator mediator) {
super();
this.mediator = mediator;
}
}
在抽象同事类中定义了每个同事类对中介者都非常了解,如此才能把请求委托给中介者完成。三个同时类都具有相同的设计,即定义一个业务接口以及每个对象必须实现的职责,同时既然是同事类就都继承AbsColleague。抽象同事类只是一个标志性父类,并没有限制子类的业务逻辑,因此每一个同事类并没有违背单一职责原则。首先来看职位接口,如下所示。
public interface IPosition {
/**
* 升职
*/
void promote();
/**
* 降职
*/
void demote();
}
职位会有升有降,职位变化如下所示。
public class Position extends AbsColleague implements IPosition {
public Position(AbsMediator mediator) {
super(mediator);
}
@Override
public void promote() {
super.mediator.up(this);
}
@Override
public void demote() {
super.mediator.down(this);
}
}
每一个职位的升降动作都委托给中介者执行,具体一个职位升降影响到谁这里没有定义,完全由中介者完成,简单而且扩展性非常好。下面我们来看工资接口,如下所示。
public interface ISalary {
/**
* 加新
*/
void increaseSalary();
/**
* 降薪
*/
void decreaseSalary();
}
工资也会有升有降,如下所示。
public class Salary extends AbsColleague implements ISalary {
public Salary(AbsMediator mediator) {
super(mediator);
}
@Override
public void increaseSalary() {
super.mediator.up(this);
}
@Override
public void decreaseSalary() {
super.mediator.down(this);
}
}
交税是公民的义务,税收接口如下所示。
public interface ITax {
/**
* 税收上升
*/
void raise();
/**
* 税收下降
*/
void drop();
}
税收的变化对我们的工资当然有影响,如下所示。
public class Tax extends AbsColleague implements ITax {
public Tax(AbsMediator mediator) {
super(mediator);
}
@Override
public void raise() {
super.mediator.up(this);
}
@Override
public void drop() {
super.mediator.down(this);
}
}
以上同事类的业务都委托给了中介者,其本类已经没有任何的逻辑了,非常简单,现在的问题是中介者类非常复杂,因为他要处理三者之间的关系。我们首先来看抽象中介者,如下所示。
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);
}
/**
* 工资增加了
*
* @param salary
*/
public abstract void up(ISalary salary);
/**
* 职位提升了
*
* @param position
*/
public abstract void up(IPosition position);
/**
* 税收增加了
*
* @param tax
*/
public abstract void up(ITax tax);
/**
* 工资降低了
*
* @param salary
*/
public abstract void down(ISalary salary);
/**
* 职位降低了
*
* @param position
*/
public abstract void down(IPosition position);
/**
* 税收降低了
*
* @param tax
*/
public abstract void down(ITax tax);
}
在抽象中介者中我们定义了6个方法,分别处理职位升降、工资升降以及税收升降的业务逻辑,采用Java多态机制来实现,我们来看实现类,如下所示。
public class Mediator extends AbsMediator {
@Override
public void up(ISalary salary) {
this.upSalary();
this.upTax();
}
@Override
public void up(IPosition position) {
this.upPosition();
this.upSalary();
this.upTax();
}
@Override
public void up(ITax tax) {
this.upTax();
this.downSalary();
}
@Override
public void down(ISalary salary) {
this.downSalary();
this.downTax();
}
@Override
public void down(IPosition position) {
this.downPosition();
this.downSalary();
this.downTax();
}
@Override
public void down(ITax tax) {
this.downTax();
this.upSalary();
}
/**
* 工资增加
*/
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 downPosition() {
System.out.println("职位降低");
}
}
该类的方法较多,但是还是非常简单的,他的12个方法分为两大类型:一类是每个业务的独立流程,比如增加工资,仅仅实现单独增加工资的职能,而不关心职位、税收是如何变化的,该类型的方法是private私有类型,只能提供本类内访问;另一类是实现抽象中介者定义的方法,完成具体的每一个逻辑,比如职位上升,同时也引起工资增加、税收增加。我们编写了一个场景类,看看运行结果,如下所示。
public class Client {
public static void main(String[] args) {
// 定义中介者
Mediator mediator = new Mediator();
// 定义各个同事类
IPosition position = new Position(mediator);
@SuppressWarnings("unused")
ISalary salary = new Salary(mediator);
@SuppressWarnings("unused")
ITax tax = new Tax(mediator);
// 职位提升了
System.out.println("===职位提升===");
position.promote();
}
}
我们回过头来分析一下设计,在接收到需求后我们发现职位、工资、税收之间有着紧密的耦合关系,如果不采用中介者模式,则每个对象都要与其他两个对象进行通信,这势必会增加系统的复杂性,同时也使系统处于僵化状态,很难实现拥抱变化的理想。通过增加一个中介者,每个同时类的职位、工资、税收都只与中介者通信,中介者封装了各个同事类之间的逻辑关系,方便系统的扩展和维护。
门面模式实现工资计算
工资计算是一件非常复杂的事情,简单来说,他是对基本工资、月奖金、岗位津贴、绩效、考勤、税收、福利等因素综合运算后的一个数字。即使设计一个HR(人力资源)系统,员工工资计算也是非常复杂的模块,但是对于外界,比如高管层,最希望看到的结果是张三拿了多少钱,李四拿了多少钱,而不是看中间的计算过程,怎么计算那是人事部门的事情。换句话说,对外界的访问者来说,他只要传递进去一个人员名称和月份即可获得工资数。而不用关心其中的计算有多么复杂,这就用得上门面模式了。
门面模式对子系统起封装作用,他可以提供一个统一的对外服务接口,我们主要实现了工资计算,通过HRFacade门面可以查询用户的工资以及出勤天数等,而不用关心这个工资或者出勤天数是怎么计算出来的,从而屏蔽了外系统对工资计算模块的内部细节依赖。我们先看子系统内部的各个实现,考勤情况如下所示。
public class Attendance {
/**
* 得到出勤天数
*
* @return
*/
public int getWorkDays() {
return (new Random()).nextInt(30);
}
}
非常简单,只用一个方法获得一个员工的出勤天数。我们再来看奖金计算,如下所示。
public class Bonus {
// 考勤情况
private Attendance atte = new Attendance();
/**
* 奖金
*
* @return
*/
public int getBonus() {
// 获得出勤情况
int workDays = atte.getWorkDays();
// 奖金计算模型
int bonus = workDays * 1800 / 30;
return bonus;
}
}
我们在这里实现了一个示意算法,实际的奖金计算是非常复杂的,与考勤、绩效、基本工资、岗位都有关系,单单一个奖金计算就可以设计出一个门面。我们再来看基本工资,这个基本上是按照职位而定的,比较固定,如下所示。
public class BasicSalary {
/**
* 获得一个人的基本工资
*
* @return
*/
public int getBasicSalary() {
return 2000;
}
}
我们定义了员工的基本工资都为2000元,没有任何浮动的余地。再来看绩效,如下所示。
public class Performance {
// 基本工资
private BasicSalary salary = new BasicSalary();
/**
* 绩效奖励
*
* @return
*/
public int getPerformanceValue() {
// 随机绩效
int perf = (new Random()).nextInt(100);
return salary.getBasicSalary() * perf / 100;
}
}
绩效按照一个非常简单的算法,即基本工资乘以一个随机的百分比。我们再来看税收,如下所示。
public class Tax {
/**
* 收取多少税金
*
* @return
*/
public int getTax() {
return (new Random()).nextInt(300);
}
}
一个计算员工薪酬的所有子元素都已经具备了,剩下的就是编写组合逻辑类,总工资的计算如下所示。
public class SalaryProvider {
// 基本工資
private BasicSalary basicSalary = new BasicSalary();
// 奖金
private Bonus bonus = new Bonus();
// 绩效
private Performance perf = new Performance();
// 税收
private Tax tax = new Tax();
/**
* 获得用户总收入
*
* @return
*/
public int totalSalary() {
return this.basicSalary.getBasicSalary() + this.bonus.getBonus()
+ this.perf.getPerformanceValue() - this.tax.getTax();
}
}
这里只是对前面的元素值做了一个加减法计算,这是对实际HR系统的简化处理,如果把这个类暴露给外系统,那么被修改的风险是非常大的,因为他的方法totalSalary是一个具体的业务逻辑。我们采用门面模式的目的是要求门面是无逻辑的,与业务无关,只是一个子系统的访问入口。门面模式只是一个技术层次上的实现,全部业务还是在子系统内实现。我们来看HR门面,如下所示。
public class HRFacade {
// 总工资情况
private SalaryProvider salaryProvider = new SalaryProvider();
// 考勤情况
private Attendance attendance = new Attendance();
/**
* 查询一个人的总收入
*
* @param name
* @param date
* @return
*/
public int querySalary(String name, Date date) {
return this.salaryProvider.totalSalary();
}
/**
* 查询一个员工一个月工作了多少天
*
* @param name
* @return
*/
public int queryWorkDays(String name) {
return this.attendance.getWorkDays();
}
}
所有的行为都是委托行为,由具体的子系统实现,门面只是提供了一个统一访问的基础而已,不做任何的校验、判断、异常等处理。我们编写了一个场景类查看运行结果,如下所示。
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("张三 本月 总收入为:" + salary);
// 再查询出勤天数
System.out.println("\n===外系统查询出勤天数===");
int workDays = facade.queryWorkDays("李四");
System.out.println("李四 本月出勤:" + workDays);
}
}
在该例中,我们使用了门面模式对薪水计算子系统进行封装,避免子系统内部复杂逻辑外泄,确保子系统的业务逻辑的单纯性,即使业务流程需要变更,影响的也是子系统内部功能,比如奖金需要与基本工资挂钩,这样的修改对外系统来说是透明的,只需要子系统内部变更即可。
最佳实践
门面模式和中介者模式之间的区别还是比较明显的,门面模式是以封装和隔离为主要任务,而中介者模式则是以调和同事类之间的关系为主,因为要调和,所以具有了部分的业务逻辑控制。两者的主要区别如下:
- 功能区别
门面模式只是增加了一个门面,他对子系统来说没有增加任何的功能,子系统若脱离门面模式是完全可以独立运行的。而中介者模式则增加了业务功能,他把各个同事类中的原有耦合关系移植到了中介者,同事类不可能脱离中介者而独立存在,除非想增加系统的复杂性和降低扩展性。
- 知晓状态不同
对门面模式来说,子系统不知道有门面存在,而对中介者来说,每个同事类都知道中介者存在,因为要依靠中介者调和同事之间的关系,他们对中介者非常了解。
- 封装程度不同
门面模式是一种简单的封装,所有的请求处理都委托给子系统完成,而中介者模式则需要有一个中心,由中心协调同事类完成,并且中心本身也完成部分业务,他属于更进一步的业务功能封装。