11.1 模式相关事件
糖醋排骨是大家都非常熟悉和喜爱的一道硬菜,今天呢,我们就以这道菜的做法为引子,来学习一下外观设计模式。
11.2 模式定义
外观模式(Facade Pattern),是软件工程中常用的一种软件设计模式,它为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。外观模式通过一个外观接口读/写子系统中各接口的数据资源,而客户可以通过外观接口读取内部资源库,不与子系统产生交互。
我们在做一个软件系统的时候,由于需求的不断变更,系统会越来越大,软件结构也会越来越复杂,因此,在使用一个类的时候,需要了解类之间的关联关系,以及调用次序,否则就可能会得到错误的结果。外观模式很好地为我们解决了这个难题,由外观处理内部各种关系,提供较高级别的简单接口,外部应用只要请求外观就能得到预期的结果。从而,不再因为类之间错综复杂的关系而烦恼。外观模式还可以兼顾系统内部各个功能和互动关系及调用的顺序。
11.3 一般化分析
首先需要一个制作糖醋排骨的,然后实现该接口,客户端调用时则需要牢记制作糖醋排骨的工序,不能错乱。
11.4 一般化实现
11.4.1 创建制作糖醋排骨的接口
package com.demo.common; /** * Created by lsq on 2018/3/20. * 制作糖醋排骨接口 */ public interface ISpareribs { //准备材料 public void prepair(); //腌制排骨 public void preserve(); //炸排骨 public void fry(); //调汁 public void juice(); }
11.4.2 制作糖醋排骨实现类
package com.demo.common; /** * Created by lsq on 2018/3/20. * 制作糖醋排骨实现类 */ public class Spareribs implements ISpareribs{ @Override public void prepair() { System.out.println("1.准备猪排骨500克,葱末、姜末、酱油、花生油、白糖、醋、料酒、盐各适量……"); } @Override public void preserve() { System.out.println("2.将排骨洗净剁成长段,用开水汆一下,捞出,加入盐、酱油腌入味……"); } @Override public void fry() { System.out.println("3.炒锅注油烧至六成热,下排骨炸至淡黄色捞出;油加热至八成,再下锅炸至金黄色捞出……"); } @Override public void juice() { System.out.println("4.炒锅留少许油烧热,下入葱花、姜末爆香,加入适量清水、酱油、醋、白糖、料酒,倒入排骨,烧开后用慢火煨至汤汁浓、排骨熟,淋上熟油,出锅即可!"); } }
11.4.3 客户端测试
import com.demo.common.ISpareribs; import com.demo.common.Spareribs; /** * Created by lsq on 2018/3/20. * 客户端应用程序 */ public class Client { public static void main(String[] args) { System.out.println("====开始做糖醋排骨……"); //创建对象实例 ISpareribs spareribs = new Spareribs(); //准备材料 spareribs.prepair(); //腌制排骨 spareribs.preserve(); //炸排骨 spareribs.fry(); //调汁 spareribs.juice(); System.out.println("====糖醋排骨制作完成!"); } }
运行结果如下图所示:
分析上面的结果,可以看到,我们得到了我们想要的结果,然而,我们必须记得制作糖醋排骨的每一个步骤,而且顺序还不能颠倒,否则做出的效果就不一样了。如何能让我们便于实现呢?外观模式为我们提供了很好的实现方式。
11.5 外观模式分析方法
11.5.1 让厨师为我们做菜
外观模式很简单,简单来说就是提供一个单独的对外接口,用于作为外部应用和内部复杂系统的桥梁。外部应用不必知道系统内部的实现细节。外观模式就像饭店中的厨师,他知道糖醋排骨该怎么做,我们去饭店吃饭,只需要点菜就好了。
11.5.2 外观模式的静态建模
11.6 糖醋排骨的外观模式实现
11.6.1 建立外观门面
1. 创建外观接口——ICookFacade
package com.demo.facade; /** * Created by lsq on 2018/3/20. * 外观模式制作糖醋排骨接口 */ public interface ICookFacade { //制作糖醋排骨 public void cookSpareribs(); }
2. 外观实现——CookFacade
package com.demo.facade; import com.demo.common.ISpareribs; import com.demo.common.Spareribs; /** * Created by lsq on 2018/3/20. * 外观模式制作糖醋排骨实现类 */ public class CookFacade implements ICookFacade{ //制作糖醋排骨接口作为对象属性字段 private final ISpareribs spareribs = new Spareribs(); //制作糖醋排骨方法 @Override public void cookSpareribs() { //准备材料 this.spareribs.prepair(); //腌制排骨 this.spareribs.preserve(); //炸排骨 this.spareribs.fry(); //调汁 this.spareribs.juice(); } }
修改客户端应用程序Client,将制作糖醋排骨的请求发送给外观CookFacade,在外观内部与制作糖醋排骨关联操作。
11.6.2 客户端测试
作为客户端程序,对于糖醋排骨的制作过程是完全透明的。
import com.demo.common.ISpareribs; import com.demo.common.Spareribs; import com.demo.facade.CookFacade; import com.demo.facade.ICookFacade; /** * Created by lsq on 2018/3/20. * 客户端应用程序 */ public class Client { public static void main(String[] args) { System.out.println("====开始做糖醋排骨……"); // //创建对象实例 // ISpareribs spareribs = new Spareribs(); // //准备材料 // spareribs.prepair(); // //腌制排骨 // spareribs.preserve(); // //炸排骨 // spareribs.fry(); // //调汁 // spareribs.juice(); ICookFacade cookFacade = new CookFacade(); cookFacade.cookSpareribs(); System.out.println("====糖醋排骨制作完成!"); } }
运行结果:
11.7 设计原则
1. 迪米特法则——最少知识原则
迪米特法则(Law of Demeter , LOD),又称最少知识原则(Least Knowledge Principle,LKP),它的意思是说一个软件实体应当尽可能少地与其他实体发生相互作用。如果两个类不直接通信,那么这两个类就不应当发生直接的相互作用,如果一个类需要调用另一个类的某个方法,可以通过第三个类转发这个调用,即只和你的密友谈话。
迪米特法则希望我们在软件设计中不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。该法则强调的是,在类的结构设计上,每一个类都应该尽量降低成员的访问权限。也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行为不要公开。迪米特法则的根本思想强调了类之间的松耦合。类之间的耦合越弱,越有利于复用,一个处在弱耦合的类被修改,不会对有关系的类产生涉及。
尽量不要在一个类中使用这样的代码:
ClassA classA = classB.getClassA();
classA.doSomeThing();
为什么呢?ClassA类内容的修改是不是会对调用类造成影响?答案是会的。这与最少知识原则不相符。修改上面的内容如下:
classB.doSomeThing();
在ClassB中增加doSomeThing()调用方法,在该方法中调用ClassA的doSomeThing方法。这样每一个类都与存在直接关系的“密友”联系,ClassA类修改就不会对调用类产生影响。
外观模式很好地应用了迪米特法则。外观模式为系统提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。它的意图就是简化系统中对子系统接口的操作,需要注意的是:外观没有封装子系统的类,只提供了简化的接口。所以,如果客户觉得没有必要,依然可以直接使用子系统的类。这是外观模式一个很好的特征:提供简化接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。外观模式封装了业务细节后将工作委托给子系统执行,将客户从一个复杂的子系统中解耦。
11.8 使用场合
1)一个软件系统的复杂度比较高,需要一个更高级别的简单接口简化对子系统的操作时;
2)当使用端与实现类之间有太多相依性,需要降低使用端与子系统或子系统间的耦合性,增加子系统的独立性时;
3)当子系统是相互依存的,需要层级化子系统,简化子系统之间的相依性的时候,可以使用外观模式。
外观模式中的角色:
1)外观角色(Facade):构成系统内部复杂子系统的单一窗口,对系统外部提供更高一级的接口API。
2)子系统角色:系统内部各种子系统各司其职,做好“本职工作”。外观角色的存在对它们并不产生任何影响,它们听从外观角色的调用,但不会反过来调用外观角色。
3)客户端角色:作为外部应用的客户端角色,不关心系统内部复杂子系统的运作情况,而只与外观角色之间通信获得数据和结果。