之前我们介绍了Adapter Pattern适配器模式,通过适配器实现两个不兼容类的配合工作。而这里,我们将会介绍另外一种结构型的设计模式——Facade Pattern外观模式。有些同志会将这二者(适配器模式、外观模式)混淆,但其二者目的有着明显的区别,前者是通过包装来改变接口以达到适配的目的;后者则是通过包装简化调用、降低耦合度
模式思想
在介绍该模式之前,我们先来看一个生活中的案例。当你下班回家后,需要把家里的电器一个一个依次打开,开电视、开空调、开灯……,而当你准备出门上班时,又要把之前打开的电器一个一个依次关掉,关电视、关空调、关灯……。这样家里电器一多以后,每次开开关关是很麻烦的。那么我们就会想如果有一个万能开关,可以直接一次性控制家中全部电器的开关。这样是不是就方便很多了?每次回家或者上班出门,只需按一下万能开关就可以完成之前的多次开关操作了
利用我们在现实世界获得的指引,将其运用于软件工程领域下,即提炼为我们今天所要介绍的设计模式——Facade Pattern外观模式,其又被称作为门面模式。在这个例子中,家中的各个电器可以称之为子系统,而这个万能开关就是外观模式中的外观角色。很多时候client不应该需要了解各个子系统的工作流程,否则会造成client与各子系统之间的高度耦合。那么我们就可以通过一个外观角色,其负责将子系统及工作流程进行封装以简化client的使用。在Facade Pattern外观模式中,涉及以下两个角色
- 子系统角色:通常来说在一个系统中会有若干个子系统(比如本文例子中的各个家用电器)。client即可以直接调用各个子系统,也可以通过外观角色来实现对各子系统的调用
- 外观角色:其通过将各子系统的方法调用逻辑、顺序的有机组合、封装。大大简化了client使用各子系统的流程及难度,降低了client与各子系统之间的耦合度
实践
现在我们通过Java实现上文提到的生活例子。首先定义实现家里各个子系统——各电器类
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
public class Tv {
public void turnOn() { System.out.println("打开电视 ... "); }
public void turnOff() { System.out.println("关闭电视"); } } ...
public class Humidifier {
public void turnOn() { System.out.println("打开加湿器 ... "); }
public void turnOff() { System.out.println("关闭加湿器"); } } ...
public class AirConditioner {
public void turnOn() { System.out.println("打开空调 ... "); }
public void turnOff() { System.out.println("关闭空调"); } }
|
由于现在我们还没引入外观模式,隔壁老王每次回到家、出门时都只好亲自操作各个家电,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class FacadePatternDemo { public static void main(String[] args) { System.out.println("---------------- Test 1 ----------------"); Tv tv = new Tv(); AirConditioner airConditioner = new AirConditioner(); Humidifier humidifier = new Humidifier();
System.out.println("【老王下班回到家,打开家中所有电器】"); tv.turnOn(); airConditioner.turnOn(); humidifier.turnOn();
System.out.println("【老王准备出门上班,关闭家中所有电器】"); tv.turnOff(); airConditioner.turnOff(); humidifier.turnOff(); } }
|
从下面的结果可以看到虽然操作有点麻烦,但结果至少符合预期
既然这里我们介绍了外观模式,就利用该模式来优化下吧。一般来说子系统一般已经先行存在了,那么实际上我们就只需要再引入一个外观角色——即家用电器的外观角色ElectricApplianceFacade类。可以看到,其内部持有了各个子系统对象,并将多个子系统的操作有机地进行组合、封装。据此实现client使用的方便简洁
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 39 40 41 42 43
|
public class ElectricApplianceFacade {
private Tv tv;
private AirConditioner airConditioner;
private Humidifier humidifier;
public ElectricApplianceFacade() { this.tv = new Tv(); this.airConditioner = new AirConditioner(); this.humidifier = new Humidifier(); }
public void turnOnAll() { tv.turnOn(); airConditioner.turnOn(); humidifier.turnOn(); }
public void turnOffAll() { tv.turnOff(); airConditioner.turnOff(); humidifier.turnOff(); } }
|
好了,现在引入了外观模式,隔壁老哥再也不用一个一个的开、关家电了,而是通过外观角色就可以很方便地实现一键开关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class FacadePatternDemo { public static void main(String[] args) { System.out.println("---------------- Test 2 ----------------"); ElectricApplianceFacade electricApplianceFacade = new ElectricApplianceFacade();
System.out.println("【老王下班回到家,打开家中所有电器】"); electricApplianceFacade.turnOnAll();
System.out.println("【老王准备出门上班,关闭家中所有电器】"); electricApplianceFacade.turnOffAll(); } }
|
其它
迪米特法则
迪米特法则,又称作最少知识原则。其内涵是一个对象对其它对象的了解程度应该尽可能的小,以降低彼此之间的耦合。显然我们可以看到,Facade Pattern外观模式就是迪米特法则的具体实践
应用
外观模式在第三方框架、组件中亦被大量使用。这里只提下其在Java日志框架下的应用。众所周知,Java下的日志框架非常繁多,有JDK Logging、Log4J、Log4J2、LogBack等,一旦变更项目中现有的日志框架选型,就需要进行大量相应的修改非常麻烦。为此SLF4J应运而生,其与其它日志框架不同的地方在于,其不是一个具体的日志框架,而是一个简单的日志框架的外观。在实际项目中,一般我们是通过SLF4J这个日志外观角色来使用日志,而不是直接显式操作、使用某一个具体的日志框架,以降低耦合度,也便于后期升级、变更具体日志框架的选型
参考文献
- Head First 设计模式 弗里曼著