文章目录
- 一、什么是外观模式(门面模式)
- 1、外观模式的结构
- 2、使用场景
- 3、外观模式的优缺点
- 4、外观模式注意事项
- 二、实例
- 1、外观模式的通用写法
- 2、智能家居案例
- 3、积分换礼品案例
- 参考资料
一、什么是外观模式(门面模式)
外观模式也叫门面模式
,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体的细节
,这样会大大降低应用程序的复杂度,提高了程序的可维护性
。其主要特征是定义了一个高层接口,让子系统更容易使用,属于结构型模式。
外观(Facade)模式是“迪米特法则”的典型应用。
1、外观模式的结构
外观模式主要包含2种角色:
- 外观角色(Facade):也称门面角色,系统对外的统一接口;
- 子系统角色(SubSystem):可以同时有一个或多个SubSystem。每个SubSystem都不是一个单独的类,而是一个类的集合。SubSystem并不知道Facade的存在,对于SubSystem而言,Facade只是另一个客户端而已(即Facade对SubSystem透明)。
2、使用场景
其实,在我们日常的编码工作中,我们都在有意无意地大量使用外观模式,但凡只要高层模块需要调度多个子系统(2个以上类对象),我们都会自觉地创建一个新类封装这些子系统,提供精简接口,让高层模块可以更加容易间接调用这些子系统的功能。尤其是现阶段各种第三方SDK,各种开源类库,很大概率都会使用外观模式。尤其是你觉得调用越方便的,外观模式使用的一般更多。
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
3、外观模式的优缺点
优点:
- 简化了客户端调用过程,无需深入了解子系统,以防给子系统带来风险。
- 减少系统依赖、松散耦合。
- 更好地划分访问层次,提高了安全性。
- 遵循迪米特法则,即最少知道原则。
缺点:
- 当增加子系统和扩展子系统行为时,可能容易带来未知风险(例如事务)。
- 不符合开闭原则。
- 某些情况下可能违背单一职责原则。
4、外观模式注意事项
我们知道,类、模块、系统之间的“通信”,一般都是通过接口调用来完成的。接口设计的好坏,直接影响到类、模块、系统是否好用。所以,我们要多花点心思在接口设计上。我经常说,完成接口设计,就相当于完成了一半的开发任务
。只要接口设计得好,那代码就差不到哪里去。
接口粒度设计得太大,太小都不好。太大会导致接口不可复用,太小会导致接口不易用。在实际的开发中,接口的可复用性和易用性需要“微妙”的权衡。针对这个问题,我的一个基本的处理原则是,尽量保持接口的可复用性,但针对特殊情况,允许提供冗余的门面接口,来提供更易用的接口
。
在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性。
但是,不能过多的或者不合理的使用外观模式,要判断使用外观模式的必要性,使用外观模式是让系统有层次,利于维护的目的。
二、实例
1、外观模式的通用写法
// 外观角色 Facade
public class Facade {
private SubSystemA a = new SubSystemA();
private SubSystemB b = new SubSystemB();
private SubSystemC c = new SubSystemC();
// 对外接口
public void do() {
this.a.doA();
this.b.doB();
this.c.doC();
}
}
// 子系统
public class SubSystemA {
public void doA() {
System.out.println("doing A stuff");
}
}
// 子系统
public class SubSystemB {
public void doB() {
System.out.println("doing B stuff");
}
}
// 子系统
public class SubSystemC {
public void doC() {
System.out.println("doing C stuff");
}
}
当多个子系统都需要同时操作时,使用一个Facade提供对外的一个接口即可:
Facade facade = new Facade();
facade.do(); // doA + doB + doC
2、智能家居案例
小明的爷爷已经60岁了,一个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。所以小明给爷爷买了智能音箱,可以通过语音直接控制这些智能家电的开启和关闭。类图如下:
//灯类
public class Light {
public void on() {
System.out.println("打开了灯....");
}
public void off() {
System.out.println("关闭了灯....");
}
}
//电视类
public class TV {
public void on() {
System.out.println("打开了电视....");
}
public void off() {
System.out.println("关闭了电视....");
}
}
//空调类
public class AirCondition {
public void on() {
System.out.println("打开了空调....");
}
public void off() {
System.out.println("关闭了空调....");
}
}
//智能音箱
public class SmartAppliancesFacade {
private Light light;
private TV tv;
private AirCondition airCondition;
public SmartAppliancesFacade() {
light = new Light();
tv = new TV();
airCondition = new AirCondition();
}
public void say(String message) {
if(message.contains("打开")) {
on();
} else if(message.contains("关闭")) {
off();
} else {
System.out.println("我还听不懂你说的!!!");
}
}
//起床后一键开电器
private void on() {
System.out.println("起床了");
light.on();
tv.on();
airCondition.on();
}
//睡觉一键关电器
private void off() {
System.out.println("睡觉了");
light.off();
tv.off();
airCondition.off();
}
}
//测试类
public class Client {
public static void main(String[] args) {
//创建外观对象
SmartAppliancesFacade facade = new SmartAppliancesFacade();
//客户端直接与外观对象进行交互
facade.say("打开家电");
facade.say("关闭家电");
}
}
3、积分换礼品案例
一个商城系统涉及到积分系统、支付系统、物流系统等借口的调用。如果所有的接口调用全部由前端发送网络请求去调用现有接口的话,一则会增加前端开发人员的难度,二则会增加一些网络请求影响页面性能。
这个时候就可以发挥门面模式的优势了。将所有现成的接口全部整合到一个类中,由后端提供统一的接口给前端调用,这样前端开发人员就不需要关心各接口的业务关系,只需要把精力集中在页面交互上。
// 实体类
public class GiftInfo {
private String name;
public GiftInfo(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
// 校验积分
public class QualifyService {
public boolean isAvailable(GiftInfo giftInfo){
System.out.println("校验" +giftInfo.getName() + "积分通过,库存通过。");
return true;
}
}
// 扣减积分
public class PaymentService {
public boolean pay(GiftInfo giftInfo){
System.out.println("扣减" + giftInfo.getName() + " 积分成功");
return true;
}
}
// 物流系统
public class ShippingService {
public String delivery(GiftInfo giftInfo){
System.out.println(giftInfo.getName() + "进入物流系统");
String shippingNo = "666";
return shippingNo;
}
}
// 使用Facade整合
public class FacadeService {
private QualifyService qualifyService = new QualifyService();
private PaymentService paymentService = new PaymentService();
private ShippingService shippingService = new ShippingService();
public void exchange(GiftInfo giftInfo){
if(qualifyService.isAvailable(giftInfo)){
if(paymentService.pay(giftInfo)){
String shippingNo = shippingService.delivery(giftInfo);
System.out.println("物流系统下单成功,物流单号是:" + shippingNo);
}
}
}
}
测试代码:
FacadeServicefacadeService = new FacadeService();
GiftInfo giftInfo = new GiftInfo("《Spring 5核心原理》");
facadeService.exchange(giftInfo);
参考资料
http://www.uml.org.cn/sjms/202105262.asp