定义
本小节我们要学习的设计模式叫做外观模式,也叫做门面模式 Facade。想象一下,我们系统随着时间的推移,系统复杂性、类之间的相互调用会变得越来越多,相比较客户角度而言,客户往往关注的是某个单一接口 API,而不会关心该 API 内部的复杂性或者内部子系统是如何运作的。
举个栗子,我们都玩过射击类游戏,游戏玩家对战的时候,需要进行射击操作,而射击牵扯到一连串的动作,比如:上子弹、瞄准、发射子弹、掉血、加分等等一系列动作,这些动作我们可以理解为各个子系统的某个接口 API,比如上子弹、发射子弹可能是武器子系统的 API,掉血、加分可能是用户子系统的 API,客户角度需要调用的接口其实只有一个,那就是射击 API,这就是具体的门面接口,门面内部的各个子系统的动作对客户是透明的,这种客户只需要调用门面接口 API 就实现了一连串内部动作(上子弹、瞄准、发射子弹、掉血、加分等)的模式其实就叫做外观模式,也叫做门面模式。
外观模式的定义是:为各个子系统的一组接口提供一致的调用窗口或门面,使得子系统更容易使用,使得复杂的子系统与客户端分离解耦。
下面用一个简单的例子来说明下使用外观模式和不使用外观模式下系统设计的差别,继续看吧。
使用实例
这里还是以上面的射击游戏为例,先来看下不使用外观模式时候的类图设计
不使用外观模式
这里代码比较简单,我们直接列出武器系统和用户系统的示例代码:
public class FireSystem {
public void fire() {
System.out.println("开火....");
}
public void useBullet() {
System.out.println("上子弹....");
}
}
...
public class UserSystem {
public void loseBlood() {
System.out.println("掉血...");
}
public void addScore() {
System.out.println("得分...");
}
}
测试类 Client 角色如下:
FireSystem fireSystem = new FireSystem();
UserSystem userSystem = new UserSystem();
fireSystem.useBullet(); // 上子弹
fireSystem.fire(); // 开火
userSystem.loseBlood(); // 掉血
userSystem.addScore(); // 加分
测试结果如下:
上子弹…
开火…
掉血…
得分…
使用外观模式
上面不使用外观模式时,可以看到客户端需要自己去直接调用各个子系统 API,系统模块多的时候对客户端十分不友好,下面我们看下使用外观模式如何解决这种问题,外观模式的类图设计如下:
这里我们引入 Facade 角色,该角色内部包含各个子系统的被委托的对象,客户端的所有请求经过 Facade 角色中转,简化了客户端操作的复杂性,Facade 代码示例如下:
public class Facade {
// 被委托的对象
private FireSystem fireSystem;
private UserSystem userSystem;
public Facade(FireSystem fireSystem, UserSystem userSystem) {
this.fireSystem = fireSystem;
this.userSystem = userSystem;
}
// 模拟射击的门面接口 API
public void shooting() {
fireSystem.useBullet(); // 上子弹
fireSystem.fire(); // 开火
userSystem.loseBlood(); // 敌人掉血
userSystem.addScore(); // 自己加分
}
}
测试 Client 调整如下:
FireSystem fireSystem = new FireSystem();
UserSystem userSystem = new UserSystem();
Facade facade = new Facade(fireSystem, userSystem);
facade.shooting(); // 射击
结果输出如下:
上子弹…
开火…
掉血…
得分…
可以看出,门面模式下,客户端接口调用的复杂性有所降低,并且内部系统和客户端之间解耦,使用门面模式下的“接待员”接口即可完成功能操作。
组成角色
外观模式的一般类图如上所示,包含的角色列举如下:
- 门面角色(Facade):门面模式自然少不了门面角色,这就是我们的 Facade 类,一般情况下,该角色会将客户端的请求委派给相应的子系统去调用,也就说该角色实际没有啥实质性的业务逻辑,只是一个单纯的委派类,用来实现客户端和子系统的解耦;
- 子系统角色(SubSystem):子系统并不是一个单一的类,而是众多类的一个系统集合。一般而言,子系统并不知道门面角色的存在,也就说对子系统而言,门面角色是完全透明的。子系统各自实现自己的功能,包括类之间的相互调用等,这些都不受门面角色的影响。
优缺点
外观模式优点:
- 实现了子系统与客户端之间关系的解耦;
- 客户端屏蔽了子系统组件,使得客户端所需处理的对象数目有所减少,使得子系统使用起来更加容易。
外观模式缺点:
- 增加新的子系统可能需要修改外观类或者客户端的源代码,违背了开闭原则;
- 外观类并没有阻断子系统被外部使用的可能性。
总结
这节我们介绍了什么是外观模式,以及外观模式的代码示例,总结下外观模式的特点及本节内容如下: