外观模式(Facade Pattern)
结构型设计模式
1. 定义
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
通俗点说,就是有很多子系统(或类或模块),它们各自有各自的功能,将它们的部分或全部功能按一定的流程去执行,就组合成一个完整的客户需要的功能,但是,客户一个个去执行这些功能,会费时费力,所以我们提供一个类(或模块或系统),把这些执行流程封装起来,封装成一个接口(方法),暴露给客户去使用。
2. 角色
- 外观角色(Facade),也叫门面角色,外观模式的核心。它被客户角色调用,它熟悉子系统的功能,内部根据客户角色的需求制定了几种功能的组合
- 子系统角色(Subsystem),许多系统的集合,一个系统提供一系列功能,功能间可能存在依赖关系,也可能毫无关系;子系统是被外观角色整合的目标
- 客户角色(Client),也就是客户端,子系统的使用者,通过外观角色进行间接调用(高大尚的说法叫解耦,客户不需要了解子系统)
3. 优点
- 解耦:客户端和子系统之间解耦
- 封装变化:子系统内部扩展和维护不会影响到客户,影响到的是外观角色
- 简化使用:对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易
- 只是提供了一个访问子系统的统一入口,但并不影响用户直接使用子系统
这里涉及到一个设计原则:最少知识原则(客户只了解Facade类)
4. 缺点
不符合开闭原则,增加子系统时,需要直接对Facade类动刀
5. 何时使用
- 当一个功能涉及到众多子系统时,就可以把这些执行流程封装成一个接口
- 层次化结构,如controller-service-dao,一个controller可以封装多个service,一个service可以封装多个dao
6. 生活中的例子
- 餐饮:客户只需要跟服务员打交道(点菜),就可以得到菜肴,不需要种菜、摘菜、洗菜、切菜、炒菜等等
- 基金、各种中介、电脑开关机、等等,太多了
反正提供服务的,差不多都有外观角色的思想
7. 与适配器模式的区别
- 适配器模式重点在于,将一个接口,转换到客户需要的接口
- 外观模式的重点在于,将一系列接口(小功能),组装成一个或多个接口(客户需要的功能)
- 在适配器模式中,客户不能直接调用待转换的接口
- 在外观模式中,客户可以直接调用待组装的接口
8. 例子
例子仅仅为了举例
一个电商系统,有很多子系统:商品系统、库存系统、订单系统、用户系统,等等
每个系统有很多功能,这里,仅编写增删改查,和例子需要用到的功能
客户端需要两个功能(当然不止两个)
- 下单:查询商品信息->查询商品库存->减少商品库存->查询用户信息-保存用户轨迹->生成订单
- 取消订单:修改订单状态-增加商品库存-保存用户轨迹
这两个功能都需要使用到各个子系统的部分功能
8.1 当没有用外观模式时,是这样的
商品系统
// 商品系统
public class GoodsSystem {
public void addGoods() {
System.out.println("增加商品");
}
public void deleteGoods() {
System.out.println("删除商品");
}
public void updateGoods() {
System.out.println("修改商品");
}
public void selectGoods() {
System.out.println("查询商品");
}
}
库存系统
// 库存系统
public class SKUSystem {
public void addSKU() {
System.out.println("增加库存");
}
public void deleteSKU() {
System.out.println("删除库存");
}
public void updateSKU() {
System.out.println("修改库存");
}
public void selectSKU() {
System.out.println("查询库存");
}
public void subtractSKUCount() {
System.out.println("减少商品存量");
}
public void addSKUCount() {
System.out.println("增加商品存量");
}
}
订单系统
// 订单系统
public class OrderSystem {
public void addOrder() {
System.out.println("增加订单");
}
public void deleteOrder() {
System.out.println("删除订单");
}
public void updateOrder() {
System.out.println("修改订单");
}
public void selectOrder() {
System.out.println("查询订单");
}
}
用户系统
// 用户系统
public class UserSystem {
public void addUser() {
System.out.println("增加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
public void updateUser() {
System.out.println("修改用户");
}
public void selectUser() {
System.out.println("查询用户");
}
public void addUserPath() {
System.out.println("保存用户轨迹");
}
}
客户端,客户下单,取消订单
// 客户端
public class Client {
public static void main(String[] args) {
// 下单:查询商品信息->查询商品库存->减少商品库存->查询用户信息-保存用户轨迹->生成订单
System.out.println("客户下单...");
GoodsSystem goodsSystem = new GoodsSystem();
goodsSystem.selectGoods(); // 查询商品信息
SKUSystem skuSystem = new SKUSystem();
skuSystem.selectSKU(); // 查询商品库存
skuSystem.subtractSKUCount(); // 减少商品库存
UserSystem userSystem = new UserSystem();
userSystem.selectUser(); // 查询用户信息
userSystem.addUserPath(); // 保存用户轨迹
OrderSystem orderSystem = new OrderSystem();
orderSystem.addOrder(); // 生成订单
System.out.println("---------");
// 取消订单:修改订单状态-增加商品库存-保存用户轨迹
System.out.println("客户取消订单...");
orderSystem.updateOrder(); // 修改订单状态
skuSystem.addSKUCount(); // 增加商品库存
userSystem.addUserPath(); // 保存用户轨迹
}
}
输出
客户下单...
查询商品
查询库存
减少商品存量
查询用户
保存用户轨迹
增加订单
---------
客户取消订单...
修改订单
增加商品存量
保存用户轨迹
结论
- 客户需要的操作多而复杂,也容易出错
- 客户与各个子系统直接交互,耦合度高
8.2 如果使用外观模式的话
现有代码不需要做修改,只增加一个类(外观角色)
// 外观角色
public class Facade {
private GoodsSystem goodsSystem;
private OrderSystem orderSystem;
private SKUSystem skuSystem;
private UserSystem userSystem;
public Facade() {
goodsSystem = new GoodsSystem();
orderSystem = new OrderSystem();
skuSystem = new SKUSystem();
userSystem = new UserSystem();
}
// 下单
public void placeOrder() {
// 下单:查询商品信息->查询商品库存->减少商品库存->查询用户信息-保存用户轨迹->生成订单
System.out.println("客户下单...");
goodsSystem.selectGoods(); // 查询商品信息
skuSystem.selectSKU(); // 查询商品库存
skuSystem.subtractSKUCount(); // 减少商品库存
userSystem.selectUser(); // 查询用户信息
userSystem.addUserPath(); // 保存用户轨迹
orderSystem.addOrder(); // 生成订单
}
// 取消订单
public void cancelOrder() {
// 取消订单:修改订单状态-增加商品库存-保存用户轨迹
System.out.println("客户取消订单...");
orderSystem.updateOrder(); // 修改订单状态
skuSystem.addSKUCount(); // 增加商品库存
userSystem.addUserPath(); // 保存用户轨迹
}
}
客户端
// 客户端
public class Client {
public static void main(String[] args) {
Facade facade = new Facade(); // 创建外观角色,让他去跟子系统打交道
facade.placeOrder(); // 下单
facade.cancelOrder(); // 取消订单
}
}
输出
客户下单...
查询商品
查询库存
减少商品存量
查询用户
保存用户轨迹
增加订单
---------
客户取消订单...
修改订单
增加商品存量
保存用户轨迹
结论
- 客户使用起来就方便多了,也不用担心出错
- 客户与子系统解耦了,不必知道子系统的内部实现,只需要跟外观角色打交道就好
- 后续子系统的升级修改,只对外观角色暴露,不会影响到客户
注意
- 并不是说子系统封装后,客户就只能通过外观角色去使用,外观只是提供一个简化的使用方式
- 客户也还是可以直接调用子系统的,并且,子系统的功能相当多,并不只是为了给“下单”“取消订单”这两个功能服务的
如买家上线商品,客户搜索商品等等,都需要使用到子系统的部分功能
- 子系统的功能,可能有依赖关系,也可能没有依赖关系
如没有库存,就不能购买了,这就是依赖;如保存用户轨迹,这个就是独立的功能,与其它子系统没有必然联系