外观设计模式

1、影院管理需求

【1】需求

组件一个家庭影院:

DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:

  • 直接用遥控器,统筹各设备开关
  • 开爆米花机
  • 放下屏幕
  • 开投影仪
  • 开音响
  • 开DVD,选DVD
  • 去拿爆米花
  • 调暗灯光
  • 播放
  • 观影结束后,关闭各种设备

【2】传统方式解决影院管理需求存在的问题分析

1)在ClientTest的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程。

2)不利于在ClientTest中去维护对子系统的操作。

3)解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法ready、play、pause、end),用来访问子系统中的一群接口。

4)也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节=>外观模式


2、外观模式基本介绍

1)外观模式(Facade),也叫“过程模式”:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

2)外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。

3)外观模式是”迪米特法则”的典型应用。


3、外观模式的结构

外观(Facade)模式包含以下主要角色:

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

4、外观模式解决影院管理问题

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
public class DVDPlayer {
private static volatile DVDPlayer instance;
private DVDPlayer() {}
public static DVDPlayer getInstance() {
if (null == instance) {
synchronized(DVDPlayer.class) {
if (null == instance) {
instance = new DVDPlayer();
}
}
}
return instance;
}

public void on() {
System.out.println(" dvd on ");
}
public void off() {
System.out.println(" dvd off ");
}
public void play() {
System.out.println(" dvd is playing ");
}
public void pause() {
System.out.println(" dvd pause.. ");
}
}

public class Popcorn {
private static volatile Popcorn instance;
private Popcorn() {}
public static Popcorn getInstance() {
if (null == instance) {
synchronized(Popcorn.class) {
if (null == instance) {
instance = new Popcorn();
}
}
}
return instance;
}

public void on() {
System.out.println(" popcorn on ");
}
public void off() {
System.out.println(" popcorn off ");
}
public void pop() {
System.out.println(" popcorn is poping ");
}
}

public class Projector {
private static volatile Projector instance;
private Projector() {}
public static Projector getInstance() {
if (null == instance) {
synchronized(Projector.class) {
if (null == instance) {
instance = new Projector();
}
}
}
return instance;
}

public void on() {
System.out.println(" projector on ");
}
public void off() {
System.out.println(" projector off ");
}
public void focus() {
System.out.println(" projector is focusing ");
}
}

public class Screen {
private static volatile Screen instance;
private Screen() {}
public static Screen getInstance() {
if (null == instance) {
synchronized(Screen.class) {
if (null == instance) {
instance = new Screen();
}
}
}
return instance;
}

public void up() {
System.out.println(" screen up ");
}
public void down() {
System.out.println(" screen down ");
}
}

public class Stereo {
private static volatile Stereo instance;
private Stereo() {}
public static Stereo getInstance() {
if (null == instance) {
synchronized(Stereo.class) {
if (null == instance) {
instance = new Stereo();
}
}
}
return instance;
}

public void on() {
System.out.println(" stereo on ");
}
public void off() {
System.out.println(" stereo off ");
}

public void up() {
System.out.println(" stereo up.. ");
}
}

public class TheaterLight {
private static volatile TheaterLight instance;
private TheaterLight() {}
public static TheaterLight getInstance() {
if (null == instance) {
synchronized(TheaterLight.class) {
if (null == instance) {
instance = new TheaterLight();
}
}
}
return instance;
}

public void on() {
System.out.println(" TheaterLight on ");
}
public void off() {
System.out.println(" TheaterLight off ");
}

public void bright() {
System.out.println(" TheaterLight bright.. ");
}
}

public class HomeTheaterFacade {
// 定义各个子系统对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private Stereo stereo;
private Projector projector;
private Screen screen;
private DVDPlayer dvDPlayer;

public HomeTheaterFacade() {
super();
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.stereo = Stereo..getInstance();
this.projector = Projector.getInstance();
this.screen = Screen.getInstance();
this.dvDPlayer = DvDPlayer.getInstance();
}

// 操作分为4步
public void ready() {
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
theaterLight.off();
}

// 播放
public void play() {
dVDPlayer.play();
}

// 暂停
public void pause() {
dVDPlayer.pause();
}

// 结束
public void end() {
popcorn.off();
theaterLight.on();
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}

public class Client {
public static void main(String[] args) {
HomeTheaterFacade h = new HomeTheaterFacade();
h.ready();
h.play();
}
}

5、外观模式的优缺点及使用场景

【1】好处
  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
【2】缺点
  • 不符合开闭原则,修改很麻烦。
【3】使用场景
  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可以将它们分离,从而提高子系统的独立性和可移植性。

6、外观模式源码解析

【1】外观模式在MyBatis框架应用分析

1)MyBatis中的Configuration去创建MetaObject对象使用到外观模式。

2)代码分析+Debug源码

【2】外观模式在tomcat中的解析

使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①对象,但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequst对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade类的对象。

1
2
3
4
5
6
7
8
public class ServletDemo extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) {

}
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
this.doPost(request, response);
}
}

RequestFacade类就使用了外观模式。先看结构图:

image-20230626233221647

为什么在此处使用外观模式呢?

定义RequestFacade类,分别实现ServletRequest,同时定义受保护的成员变量Request,并且方法的实现调用Request的实现。然后,将RequestFacade上转为ServletRequest传给servlet的service方法,这样即使在servlet中被下转为RequestFacade,也不能访问受保护的成员变量对象中的方法。既用了Request,又能防止其中方法被不合理的访问。


7、外观模式的注意事项和细节

1)外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性。

2)外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展。

3)通过合理的使用外观模式,可以帮我们更好的划分访问的层次。

4)当系统需要进行分层设计时,可以考虑使用Facade模式。

5)在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性。

6)不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。