GeekIBLi

设计模式之美-门面模式

2021-08-07

门面模式

门面模式又叫做外观模式,提供统一的一个接口,用来访问子系统中的一群接口;
门面模式定义了一个高层接口,让子系统更容易使用;门面模式属于结构型模式;

类图

Facade 门面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Facade {
// 继承各个子系统功能,进行封装,一定程度上不遵循单一职责原则
SubSystemA subSystemA = new SubSystemA();
SubSystemB subSystemB = new SubSystemB();
SubSystemC subSystemC = new SubSystemC();

public void doA(){
subSystemA.doA();
}
public void doB(){
subSystemB.doB();
}
public void doC(){
subSystemC.doC();
}
}

子系统A

1
2
3
public class SubSystemA {
public void doA(){}
}

子系统B

1
2
3
public class SubSystemB {
public void doB(){}
}

子系统C

1
2
3
public class SubSystemC {
public void doC(){}
}

以上Facade类集成了三个子系统的类,在自己定义的方法中,并不是Facade自己实现的逻辑,而是
调用了对应子系统的方法,这种实现方式叫做门面模式;是不是很简单;

看到这是不是有点似曾相识呢,没错,我们天天都在写的Controller,Service,Dao不就是门面模式吗,
没错,只不过把这种方式形成方法论,也就有了所谓的门面模式!

举个栗子

一些商业博客会有一个功能,就是发表文章或者评论点赞会获得一些积分啊,虚拟币啊,然后会有积分商城,在里面可以免费的兑换商品,其实很难凑的够积分,不够费劲的… 好了,结合伪代码来体验门面模式👇👇👇:

下面是一些演示所需要的类:

关系图如下

PaymentService 支付服务

1
2
3
4
5
6
public class PaymentService {
public boolean pay(GiftInfo giftInfo){
System.out.println("扣减" + giftInfo.getName() + "积分成功!");
return true;
}
}

QualityService 库存服务

1
2
3
4
5
6
public class QualityService {
public boolean isAvailable(GiftInfo giftInfo){
System.out.println("校验" + giftInfo.getName() + "积分通过,库存充足!");
return true;
}
}

ShipService 物流服务

1
2
3
4
5
6
public class ShipService {
public String doShip(GiftInfo giftInfo){
System.out.println(giftInfo.getName() + "生成物流订单");
return String.valueOf(System.currentTimeMillis());
}
}

客户端 非门面模式写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
QualityService qualityService = new QualityService();
PaymentService paymentService = new PaymentService();
ShipService shipService = new ShipService();

GiftInfo giftInfo = new GiftInfo(" 《Java编程思想》 ");

if (!qualityService.isAvailable(giftInfo)){
System.err.println("Quality not enough!");
}

if (!paymentService.pay(giftInfo)){
System.err.println("Pay error!");
}

String shipNo = shipService.doShip(giftInfo);
System.err.println("Order shipNo:" + shipNo);

}

这种写法会将库存,支付和物流等服务都暴露给调用方,是很不安全的,而且造成客户端依赖严重,代码臃肿;

门面模式写法

1
2
3
4
5
6
public static void main(String[] args) {
FacadeService facadeService = new FacadeService();
GiftInfo giftInfo = new GiftInfo(" 《Java编程思想》 ");
String shipNo = facadeService.doOrder(giftInfo);
System.err.println("Order shipNo:" + shipNo);
}

上面这种就是门面模式的写法👆 , 相信大家应该很熟悉吧,这样的话,暴露给客户端就一个订单服务就可以了!

应用场景

  • 子系统越来越复杂,增加门面模式提供简单的接口,给用户使用;
  • 构建多层的系统接口,利用门面对象作为每层的入口,简化层之间的调用

应用

Spring JdbcUtils
Mybatis configuration
Tomcat requestFacade responseFacade

优点

  • 简化了调用过程,无需深入了解子系统,以防止给子系统带来风险

根据上面礼品兑换的逻辑,用户根本不care你底层的兑换逻辑,什么库存啊,支付状态啊,生成订单逻辑等等,对于用户来说,我只需要一步下单即可;

  • 减少系统依赖,松耦合

这一点也是相对客户端来说,客户端只关心的的订单服务就好了,其他的库存,供应链等都不关系;

  • 更好的划分访问层次,提高了安全性

合理的划分层次,减少底层系统的暴露,仅仅暴露一些必要的状态和接口,这一点大家应该都知道的,像service层调用Dao层,而不能在service层直接访问数据库;

  • 遵循迪米特法则

缺点

  • 当子系统的功能需要扩展或者修改的时候,上层封装可能要面临修改的风险,这样增加了后期的维护成本,也不遵循开闭原则
  • 可能会违背单一职责原则

门面模式和代理模式的区别

简单来说,门面模式就是一种代理模式,是属于静态代理的模式;但是和静态代理又有一些区别,门面模式的侧重点在于对底层的封装,而静态代理则终于对代理对象的增强,除了调用受委托对象的方法之外,可以扩展额外的功能;
很多时候会把门面模式注入成单例,比如一些全局的Util,还有我们常见的一些Controller等等;