GoF设计模式(八):Facade Pattern 外观模式

之前我们介绍了Adapter Pattern适配器模式,通过适配器实现两个不兼容类的配合工作。而这里,我们将会介绍另外一种结构型的设计模式——Facade Pattern外观模式。有些同志会将这二者(适配器模式、外观模式)混淆,但其二者目的有着明显的区别,前者是通过包装来改变接口以达到适配的目的;后者则是通过包装简化调用、降低耦合度

abstract.jpeg

模式思想

在介绍该模式之前,我们先来看一个生活中的案例。当你下班回家后,需要把家里的电器一个一个依次打开,开电视、开空调、开灯……,而当你准备出门上班时,又要把之前打开的电器一个一个依次关掉,关电视、关空调、关灯……。这样家里电器一多以后,每次开开关关是很麻烦的。那么我们就会想如果有一个万能开关,可以直接一次性控制家中全部电器的开关。这样是不是就方便很多了?每次回家或者上班出门,只需按一下万能开关就可以完成之前的多次开关操作了

利用我们在现实世界获得的指引,将其运用于软件工程领域下,即提炼为我们今天所要介绍的设计模式——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
/**
* 外观模式Demo
*/
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();
}
}

从下面的结果可以看到虽然操作有点麻烦,但结果至少符合预期

figure 1.png

既然这里我们介绍了外观模式,就利用该模式来优化下吧。一般来说子系统一般已经先行存在了,那么实际上我们就只需要再引入一个外观角色——即家用电器的外观角色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
/**
* 外观模式Demo
*/
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();
}
}

figure 2.png

其它

迪米特法则

迪米特法则,又称作最少知识原则。其内涵是一个对象对其它对象的了解程度应该尽可能的小,以降低彼此之间的耦合。显然我们可以看到,Facade Pattern外观模式就是迪米特法则的具体实践

应用

外观模式在第三方框架、组件中亦被大量使用。这里只提下其在Java日志框架下的应用。众所周知,Java下的日志框架非常繁多,有JDK Logging、Log4J、Log4J2、LogBack等,一旦变更项目中现有的日志框架选型,就需要进行大量相应的修改非常麻烦。为此SLF4J应运而生,其与其它日志框架不同的地方在于,其不是一个具体的日志框架,而是一个简单的日志框架的外观。在实际项目中,一般我们是通过SLF4J这个日志外观角色来使用日志,而不是直接显式操作、使用某一个具体的日志框架,以降低耦合度,也便于后期升级、变更具体日志框架的选型

参考文献

  1. Head First 设计模式 弗里曼著
0%