博客来源:http://wangym.iteye.com/blog/743944
近期参与项目研发,调用接口时遇一事不爽,比如在调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(); } }
门面不参与接口类内的业务逻辑
门面对象只是提供一个访问接口的路径而已,不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题:接口必须依赖门面才能被访问。如果在门面对象内有业务逻辑,则应建立一个封装类,封装完毕后提供给门面对象。