设计模式实战-门面模式
1、定义
本小节我们要学习的设计模式叫做外观模式,也叫做门面模式 Facade。想象一下,我们系统随着时间的推移,系统复杂性、类之间的相互调用会变得越来越多,相比较客户角度而言,客户往往关注的是某个单一接口 API,而不会关心该 API 内部的复杂性或者内部子系统是如何运作的。
举个栗子,我们都玩过射击类游戏,游戏玩家对战的时候,需要进行射击操作,而射击牵扯到一连串的动作,比如:上子弹、瞄准、发射子弹、掉血、加分等等一系列动作,这些动作我们可以理解为各个子系统的某个接口 API,比如上子弹、发射子弹可能是武器子系统的 API,掉血、加分可能是用户子系统的 API,客户角度需要调用的接口其实只有一个,那就是射击 API,这就是具体的门面接口,门面内部的各个子系统的动作对客户是透明的,这种客户只需要调用门面接口 API 就实现了一连串内部动作(上子弹、瞄准、发射子弹、掉血、加分等)的模式其实就叫做外观模式,也叫做门面模式。
外观模式的定义是:为各个子系统的一组接口提供一致的调用窗口或门面,使得子系统更容易使用,使得复杂的子系统与客户端分离解耦。
下面用一个简单的例子来说明下使用外观模式和不使用外观模式下系统设计的差别,继续看吧。
2、使用实例
这里还是以上面的射击游戏为例,先来看下不使用外观模式时候的类图设计:
2.1 不使用外观模式
这里代码比较简单,我们直接列出武器系统和用户系统的示例代码:
/**
* @Desc 武器子系统
* @Author chaozhou
*/
public class FireSystem {
public void fire() {
System.out.println("开火....");
}
public void useBullet() {
System.out.println("上子弹....");
}
}
...
/**
* @Desc 用户子系统
* @Author chaozhou
*/
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(); // 加分
测试结果如下:
上子弹… 开火… 掉血… 得分…
2.2 使用外观模式
上面不使用外观模式时,可以看到客户端需要自己去直接调用各个子系统 API,系统模块多的时候对客户端十分不友好,下面我们看下使用外观模式如何解决这种问题,外观模式的类图设计如下:
这里我们引入 Facade 角色,该角色内部包含各个子系统的被委托的对象,客户端的所有请求经过 Facade 角色中转,简化了客户端操作的复杂性,Facade 代码示例如下:
/**
* @Desc Facade 角色
* @Author chaozhou
*/
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(); // 射击
结果输出如下:
上子弹… 开火… 掉血… 得分…
可以看出,门面模式下,客户端接口调用的复杂性有所降低,并且内部系统和客户端之间解耦,使用门面模式下的“接待员”接口即可完成功能操作。
3、组成角色
外观模式的一般类图如上所示,包含的角色列举如下:
- 门面角色(Facade):门面模式自然少不了门面角色,这就是我们的 Facade 类,一般情况下,该角色会将客户端的请求委派给相应的子系统去调用,也就说该角色实际没有啥实质性的业务逻辑,只是一个单纯的委派类,用来实现客户端和子系统的解耦;
- 子系统角色(SubSystem):子系统并不是一个单一的类,而是众多类的一个系统集合。一般而言,子系统并不知道门面角色的存在,也就说对子系统而言,门面角色是完全透明的。子系统各自实现自己的功能,包括类之间的相互调用等,这些都不受门面角色的影响。
4、优缺点
外观模式优点:
- 实现了子系统与客户端之间关系的解耦;
- 客户端屏蔽了子系统组件,使得客户端所需处理的对象数目有所减少,使得子系统使用起来更加容易。
外观模式缺点:
- 增加新的子系统可能需要修改外观类或者客户端的源代码,违背了开闭原则;
- 外观类并没有阻断子系统被外部使用的可能性。
5、总结
这节我们介绍了什么是外观模式,以及外观模式的代码示例,总结下外观模式的特点及本节内容如下:
外观模式学完之后,大家可以类比下 Adapter 模式、Bridge 模式以及 Decorator 模式,比较下这几种模式的侧重点与不同
- 权威报告预测比特币在2018年“王位”不保
- Linux下FTP环境部署梳理(vsftpd和proftpd)
- Silverlight如何与JS相互调用
- Docker容器学习梳理--私有仓库Registry使用
- 从插件重构看如何提升测试质量与效率
- 巧用WinRAR+Javascript解决activeX的自动安装问题
- 在网页中实现QQ的屏幕截图功能
- Activity之间传递参数
- linux下rsync和tar增量备份梳理
- 重温Delphi之:面向对象
- Android新手之旅(15) Win7下配置遇到的问题
- 重温Delphi之:如何定义一个类
- Android新手之旅(2) 新手问题
- Android新手之旅(2) 新手问题
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 解决pyPdf和pyPdf2在合并pdf时出现异常的问题
- rxjs里scan operators的用法
- rxjs里switchMap operators的用法
- rxjs里concatMap operators的用法
- rxjs里takeWhile operators的用法
- Python sql注入 过滤字符串的非法字符实例
- rxjs里combineLatest operators的用法
- rxjs里withLatestFrom operators的用法
- rxjs里distinctUntilChanged operators的用法
- pycharm安装及如何导入numpy
- rxjs里debounceTime operators的用法
- rxjs里delay operators的用法
- Android使用FontMetrics对象计算位置坐标
- rxjs里scan和mergeScan operators的用法
- Android自定义控件的步骤