近期参与项目研发,调接口时遇一事不爽,比如在调C方法前必须得先调A方法,成功后再调B方法拿到返回结果后,最后才可以调C方法,而且接口调用还会随需求变化而变更,比如可能还得再增加几个步骤......
门面模式(亦称外观模式Facade Pattern)的思想很好地解决了这一问题,当然,它的优点也不止如此。
场景举例
投递信件的过程是:首先写信的内容 -> 其次写信封 -> 把信放到信封里 -> 然后邮递
接口类:ILetterProcess
/**
* @author xuanyin
*
*/
public interface ILetterProcess {
/**
* 首先写信的内容
*
* @param context
*/
void writeContext(String context);
/**
* 其次写信封
*
* @param address
*/
void fillEnvelope(String address);
/**
* 把信放到信封里
*/
void letterIntoEnvelope();
/**
* 然后邮递
*/
void sendLetter();
}
实现类:LetterProcess
(代码略)
场景类:Client
/**
* @author xuanyin
*
*/
public class Client {
public static void main(String[] args) {
// 创建一个处理信件的过程
ILetterProcess letterProcess = new LetterProcess();
// 开始写信
letterProcess.writeContext("context");
// 开始写信封
letterProcess.fillEnvelope("address");
// 把信放到信封,并封装好
letterProcess.letterIntoEnvelope();
// 跑到邮局把信塞到邮件,投递
letterProcess.sendLetter();
}
}
上述过程,与高内聚的要求相差甚远,更不要说迪米特法则、接口隔离原则。调用方首先得知道这四个步骤,而且还不能颠倒顺序。这在面向对象的编程中是极度地不适合的,它根本就没有完成一个类所具有的单一职责。更何况,如果信件多了就非常麻烦,每封信都要这样运转一遍。
模式演变
增加一邮局类,提供信件代发业务,只需把信件的必要信息告之,就代为发送,即提供以上四个步骤的一体化服务。
邮局类:PostOffice
/**
* @author xuanyin
*
*/
public class PostOffice {
/**
*
*/
private ILetterProcess letterProcess = new LetterProcess();
/**
* 写信,封装,投递,一体化了
*
* @param context
* @param address
*/
public void sendLetter(String context, String address) {
// 帮你写信
letterProcess.writeContext(context);
// 写好信封
letterProcess.fillEnvelope(address);
// 把信放到信封中
letterProcess.letterIntoEnvelope();
// 邮递信件
letterProcess.sendLetter();
}
}
注意,这个新增加的邮局类就是“门面对象”
增加邮局类后,在场景类中调用时就简单多了
场景类:Client
/**
* @author xuanyin
*
*/
public class Client {
public static void main(String[] args) {
// 现代化邮局
PostOffice postOffice = new PostOffice();
// 写完内容和地址后邮局一体化代发,不必关心其中过程
postOffice.sendLetter("context", "address");
}
}
可以看到,在场景类中的调用简化了很多,只需要把信息提交过去就成了,不必再知道投递过程中的所有方法和顺序了。而且这样做的可扩展性还非常好,比如在非常时期,警察需要对信件进行安全检查,就可以按如下实现。
警察类:Police
/**
* @author xuanyin
*
*/
public class Police {
/**
* 警察对信件进行检查
*
* @param context
* @return true检查通过|false检查不通过
*/
public boolean checkLetter(ILetterProcess letterProcess) {
return true; // or false
}
}
再看下当增加检查信件步骤后,邮局类的一些变更。
邮局类:PostOffice
/**
* @author xuanyin
*
*/
public class PostOffice {
/**
*
*/
private ILetterProcess letterProcess = new LetterProcess();
/**
*
*/
private Police police = new Police();
/**
* 写信,封装,投递,一体化了
*
* @param context
* @param address
*/
public void sendLetter(String context, String address) {
// 帮你写信
letterProcess.writeContext(context);
// 写好信封
letterProcess.fillEnvelope(address);
// 警察检查信件
if (police.checkLetter(letterProcess)) {
// 把信放到信封中
letterProcess.letterIntoEnvelope();
// 邮递信件
letterProcess.sendLetter();
}
}
}
增加警察检查信件这个逻辑后,并没有改动调用端的代码,也没有改变对外暴露的接口和方法,只是改变内部的处理逻辑,所以对调用端是透明的。这是一个非常棒的设计,这就是门面模式。
注意事项
一、一个接口类可以用多个门面
一般情况下,一个接口类只要有一个门面对象就足够了,但下列情况可以有多个:
1.门面对象已庞大到一定的程度:比如超过一定行数的代码,虽然都是非常简单的委托操作,也建议拆分成多个门面。拆分时可按功能拆分,比如一个数据库操作,拆分成查询门面对象,删除门面对象,更新门面对象等。
2.给接口类提供不同的访问路径:我的理解是,比如模块1通过门面对象A可以完整地访问所有逻辑,但模块2是受限制的,如果仍使用门面对象A,那是无法实现受限的。这时就需要新增门面对象B,提供模块2应得的方法。但在新增门面对象B时,无需重新实现。因在面向对象编程中,应尽量保持相同的代码只写一遍,避免以后修改时到处都要修改的杯具发生。如:
/**
*
* @author xuanyin
*
*/
public class Facade2 {
// 引用原有的门面
private Facade1 facade1 = new Facade1();
/**
* 因提供的方法在原有门面中已实现,故此处通过引用,无需重复实现
*/
public void methodB() {
this.facade1.methodB();
}
}
二、门面不参与接口类内的业务逻辑
门面对象只是提供一个访问接口的路径而已,不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题:接口必须依赖门面才能被访问。如果在门面对象内有业务逻辑,则应建立一个封装类,封装完毕后提供给门面对象。