外观模式
# 外观模式
# 外观模式基本介绍
外观模式(Facade),也叫 “过程模式”:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了 一个高层接口,这个接口使得这一子系统更加容易使用。
模式核心:
外观模式通过一个统一的接口(Facade)封装复杂子系统的调用逻辑,为客户端提供简单易用的操作接口。其核心是降低系统复杂度,避免客户端直接与多个子系统交互。
角色划分:
- 子系统类
- 指模块或者子系统,处理 Facade 对象指派的任务,他是功能的实际提供者
- 外观类
- 为调用端提供统一的调用接口,外观类知道哪些子系统负责处理请求,从而将调用端的请求代理给适当子系统对象
- 调用者(Client)
- 外观接口的调用者
外观模式可以理解为转换一群接口,客户只要调用一个接口,而不用调用多个接口才能达到目的。
比如:在 PC 上安装软件的时候经常有一键安装选项(省去选择安装目录、安装的组件等等),还有就是手机的重启功能(把关机和启动合为一个操作)。
外观模式就是解决多个复杂接口带来的使用困难,起到简化用户操作的作用
# 代码示例
# 子系统类
public class DVDPlayer {
// 使用单例模式, 使用饿汉式(静态变量)
@Getter
private static DVDPlayer instance = new DVDPlayer();
public void on() {
System.out.println("DVD Player turned on");
}
public void off() {
System.out.println("DVD Player turned off");
}
public void play() {
System.out.println("DVD is playing");
}
public void pause() {
System.out.println("DVD playback paused");
}
}
public class Popcorn {
@Getter
private static Popcorn instance = new Popcorn();
public void on() {
System.out.println("Popcorn machine turned on");
}
public void off() {
System.out.println("Popcorn machine turned off");
}
public void pop() {
System.out.println("Popcorn is popping");
}
}
public class Screen {
@Getter
private static Screen instance = new Screen();
public void up() {
System.out.println("Screen up ");
}
public void down() {
System.out.println("Screen down ");
}
}
// ...
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
# 外观类(Facade)
public class HomeTheaterFacade {
// 定义各个子系统对象
private final TheaterLight theaterLight;
private final Popcorn popcorn;
private final Stereo stereo;
private final Projector projector;
private final Screen screen;
private final 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();
projector.focus();
// 开启音响
stereo.on();
stereo.setVolume(5);
// 启动DVD播放器
dvdPlayer.on();
// 调暗灯光
theaterLight.dim();
}
/**
* 开始播放电影
*/
public void play() {
dvdPlayer.play();
}
/**
* 暂停播放
*/
public void pause() {
dvdPlayer.pause();
}
/**
* 结束观影:关闭所有设备并恢复环境
*/
public void end() {
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dvdPlayer.off();
}
}
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
# 调用者(Client)
public class Client {
public static void main(String[] args) {
// 创建外观对象,简化客户端操作
HomeTheaterFacade facade = new HomeTheaterFacade();
System.out.println("准备观影...");
facade.ready();
System.out.println("\n开始播放电影...");
facade.play();
System.out.println("\n暂停观影...");
facade.pause();
System.out.println("\n结束观影...");
facade.end();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 外观模式在 MyBatis 框架应用的源码分析
MyBatis 中的 Configuration 去创建 MetaObject 对象使用到外观模式。
在 MyBatis 中,
MetaObject
是一个用于 封装对象并提供便捷操作(如反射访问字段、方法调用) 的类,- 而
Configuration
负责管理 MyBatis 的整体配置信息,其中就包括MetaObject
的创建。
# 代码分析
# 1、Configuration 如何创建 MetaObject
?
- 在 MyBatis 源码
org.apache.ibatis.session.Configuration
类中,可以看到MetaObject
是通过MetaObject.forObject()
方法创建的:
public class Configuration {
protected final ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected final ObjectFactory objectFactory = new DefaultObjectFactory();
protected final ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 创建 MetaObject 的方法
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
2
3
4
5
6
7
8
9
10
11
调用 newMetaObject(Object object)
这个方法时:
- 隐藏了
MetaObject
创建的细节,只暴露了newMetaObject()
这个简单接口。 MetaObject.forObject()
方法内部会自动调用ObjectWrapperFactory
、ObjectFactory
和ReflectorFactory
处理对象。- 让
Configuration
作为外观(Facade),统一封装MetaObject
的创建过程。
# 2、MetaObject.forObject()
方法内部逻辑
public class MetaObject {
private final Object originalObject;
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper)object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map)object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection)object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
return object == null ? SystemMetaObject.NULL_META_OBJECT : new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
// ...
}
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
核心功能:
- 屏蔽了
MetaObject
的复杂构造逻辑,无论object
是普通 JavaBean、Map
还是Collection
,都会选择合适的Wrapper
进行封装。 - 通过
ObjectWrapperFactory
判断是否有自定义封装,如果有,则交给getWrapperFor()
处理。 - 代码中
new BeanWrapper(...)
、new MapWrapper(...)
、new CollectionWrapper(...)
隐藏了具体的封装策略,调用者不需要关心对象的具体类型,直接获取MetaObject
即可。
# 3、体现了外观模式的地方
外观模式的核心思想 —— 对外提供简单接口,隐藏复杂细节。
在 Configuration
中:
- 对外提供
newMetaObject()
方法,让Configuration
作为 Facade(外观),对MetaObject
的创建过程进行封装。 - 屏蔽
MetaObject
内部的ObjectWrapper
处理逻辑(Bean、Map、Collection),调用者只需传入一个对象,即可得到MetaObject
,无需关心内部逻辑。 MetaObject.forObject()
进一步封装细节,内部选择合适的Wrapper
处理对象,使得Configuration
只需要调用newMetaObject()
,就能获得封装好的MetaObject
。
# 外观模式的注意事项和细节
外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
外观模式对客户端与子系统的耦合关系 - 解耦,让子系统内部的模块更易维护和扩展
通过合理的使用外观模式,可以帮我们更好的划分访问的层次
当系统需要进行分层设计时,可以考虑使用 Facade 模式
在维护一个遗留的大型系统时,可能这个系统己经变得非常难以维护和扩展,此时可以考虑为新系统开发一个 Facade 类,来提供遗留系统的比较清晰简单的接口,让新系统与 Facade 类交互,提高复用性
不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维 护为目的。