门面模式:点击即用

🍐⚱️:这章的重点在于教会我们这个单词的发音:Facade/fə’sɑ:d/。
程序这东西总是会变得越来越大。随着时间的推移,程序中的类会越来越多,而且它们之间相互关联,这会导致程序结构也变得越来越复杂。我们在使用这些类之前,必须先弄清楚它们之间的关系,注意正确的调用顺序。特别是在调用大型程序进行处理时,我们需要格外注意那些数量庞大的类之间错综复杂的关系。
不过与其这么做,不如为这个大型程序准备一个「窗口」。这样,我们就不必单独地关注每个类了,只需简单地对「窗口」提出请求即可。这个「窗口」就是本文介绍的门面模式(外观模式)。
Provide a unified interface to a set of interfaces in a subsystem. Facade Pattern defines a higher-level interface that makes the subsystem easier to use.——GoF
门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。
这里的子系统可以理解为一个完整的系统,也可以是更细粒度的类或者模块。
Facade 是一个源自法语的单词,它的意思是「建筑物的正面」。
使用门面模式可以为互相关联在一起的错综复杂的类整理出高层接口(API)。其中的 Facade 角色可以让系统对外只有一个简单的接口(API)。而且 Facade 角色还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。


登场角色:
Facade(窗口):Facade角色是代表构成系统的许多其他角色的「简单窗口」。Facade角色向系统外部提供高层接口(API)。AdditionalFacade(附加外观):避免多种不相关的功能污染单一外观,使其变成又一个复杂结构。客户端和其他外观都可使用附加外观。- 构成系统的许多其他角色(复杂子系统):这些角色各自完成自己的工作,它们并不知道
Facade角色。Facade角色调用其他角色进行工作,但是其他角色不会调用Facade角色。 Client(请求者):Client角色负责调用Facade角色。在 GoF 书中,Client角色并不包含在门面模式中。
ME:《图解设计模式》的例子
Facade的构造方法为private。例子中 Facade 实现的唯一方法为static方法。
拓展思路:
- Facade 让复杂的东西看起来简单。接口(API)变少了,程序与外部关联关系弱化了,这样更容易使我们的包(类的集合)作为组件被复用。设计包时,需要考虑类的可见性。如果让外部(包的外部)看到了类,包内部代码的修改就会变得困难。
- 递归地使用 Facade 模式:假设现在有几个持有 Facade 角色的类的集合。那么,我们可以通过整合这几个集合来引入新的 Facade 角色。也就是说,我们可以递归地使用 Facade 模式。在超大系统中,往往都含有非常多的类和包。如果我们在每个关键的地方都使用 Facade 模式,那么系统的维护就会变得轻松很多。
- 开发者不愿意创建 Facade 角色的原因——心理原因:下意识回避创建 Facade 角色的开发人员:要么对类之间的所有依赖关系记得一清二楚,要么对自己的技术感到骄傲,要么不懂装懂。当某个程序员得意地说出「啊,在调用那个类之前需要先调用这个类。在调用那个方法之前需要先在这个类中注册一下」的时候,就意味着我们需要引入 Facade 角色了。对于那些能够明确地用语言描述出来的知识,我们不应该将它们隐藏在自己脑袋中,而是应该用代码将它们表现出来。
门面模式有点类似 站内文章面向对象编程 OOP 和 站内文章设计原则 提到的:
- 迪米特法则(最小知识原则)
- 接口隔离原则
- 封装、抽象思想
如果门面接口不多,我们完全可以将它跟非门面接口放到一块,也不需要特殊标记,当作普通接口来用即可。如果门面接口很多,我们可以在已有的接口之上,再重新抽象出一层,专门放置门面接口,从类、包的命名上跟原来的接口层做区分。如果门面接口特别多,并且很多都是跨多个子系统的,我们可以将门面接口放到一个新的子系统中。
| 优点 | 缺点 |
|---|---|
| 让自己的代码独立于复杂子系统。 | 外观可能成为与程序中所有类都耦合的上帝对象。 |
相关的设计模式:
- [[适配器模式:转接头|适配器模式]]:外观模式为现有对象定义了一个新接口,适配器模式则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
- 站内文章抽象工厂模式:可以将抽象工厂模式看作生成复杂实例时的门面模式。因为它提供了「要想生成这个实例只需要调用这个方法就 OK 了」的简单接口。
- 站内文章单例模式:有时会使用单例模式创建
Facade角色,因为在大部分情况下一个外观对象就足够了。 - [[中介者模式:只有一个仲裁者|中介者模式]]:两种模式都尝试在大量紧密耦合的类中组织起合作。
- 在门面模式中,
Facade角色单方面地使用其他角色来提供高层接口(API)。 - 在 Mediator 模式中,
Mediator角色作为Colleague角色间的仲裁者负责调停。 - 可以说,Facade 模式是单向的,而
Mediator角色是双向的。
- 在门面模式中,
- [[享元模式:共享实例|享元模式]]:享元模式展示了如何生成大量的小型对象,外观模式则展示了如何用一个对象来代表整个子系统。
- 站内文章代理模式:它们都缓存了一个复杂实体并自行对其进行初始化。代理与其服务对象遵循同一接口,使得自己和服务对象可以互换,在这一点上它与外观不同。
应用场景
解决易用性问题
门面模式可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。比如,Linux 系统调用函数就可以看作一种「门面」。它是 Linux 操作系统暴露给开发者的一组「特殊」的编程接口,它封装了底层更基础的 Linux 内核调用。再比如,Linux 的 Shell 命令,实际上也可以看作一种门面模式的应用。它继续封装系统调用,提供更加友好、简单的命令,让我们可以直接通过执行命令来跟操作系统交互。
解决性能问题
假设有一个系统 A,提供了 a、b、c、d 四个接口。系统 B 完成某个业务功能,需要调用 A 系统的 a、b、d 接口。利用门面模式,我们提供一个包裹 a、b、d 接口调用的门面接口 x,给系统 B 直接使用。
如果实在网络通信场景,原来对 3 个接口的依次调用,可以简化为一个接口的调用,有效减少网络通信成本,提高请求方的响应速度。
解决分布式事务问题
在一个分布式系统中,要支持两个接口调用在一个事务中执行,是比较难实现的。虽然我们可以通过引入分布式事务框架或者事后补偿的机制来解决,但代码实现都比较复杂。
而最简单的解决方案是,利用数据库事务或者 Spring 框架提供的事务,在一个事务中,执行两个 SQL 操作,要求两个 SQL 操作要在一个接口中完成。所以,我们可以借鉴门面模式的思想,再设计一个包裹这两个操作的新接口,让新接口在一个事务中执行两个 SQL 操作。
本文参考
- 《图解设计模式》
- 本科生课程笔记《程序设计中级实践&设计模式》 - TJU 🍐⚱️
- 极客时间专栏 - 设计模式之美 - 王争
- 外观设计模式(门面模式)





