设计模式:外观模式(Facade)
当一个系统功能越来越强,子系统越来越多时,客户对系统的访问会随之变得越来越复杂。
特别是当前微服务架构的流行,子系统数量快速膨胀,这时要完成一笔业务,需要跨越多个子系统;如果子系统内部发生改变,客户端也要跟着改变,这违背了 开闭原则,也违背了迪米特法则。
所以就有必要是为多个子系统提供一个统一的门面(外观对象),客户端只与 门面 通信,通过 门面 来隐藏系统的复杂性,降低耦合度,这就是外观模式的作用。
外观模式在实际生活中的典型应用,如政务中心的 一个窗口办理,一站式审批。原本群众需要来回跑多个部门才可能办理成功的业务,现由一个窗口统一办理,不同部门的差异通过这一个办理窗口隐藏和屏蔽。
在日常编码工作中,通常都在无意中使用了外观模式。当是高层模块需要调度多个子系统时,都会自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加容易地间接调用这些子系统的功能。尤其是现阶段各种第三方SDK、开源类库,很大概率都会使用外观模式。
模式定义
外观(Facade)模式:又称为门面模式,是一种 对象结构型 模式。 外观模式定义了一个高层接口(即为多个子系统提供统一的门面),客户端与通过 门面 与子系统通信,使这些子系统更加容易被访问。
外部客户端不关心内部子系统的具体细节,这样大大降低应用程序的复杂度,提高了程序的可维护性。
模式分析
外观模式是 迪米特法则 的典型应用,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
由客户端与多个子系统联系,引入外观模式后,客户端与一个新的外观类联系。外观模式是实现代码重构以便达到迪米特法则要求的一个强有力的武器。
外观模式要求客户端与子系统的通信必须通过一个统一的外观对象来进行,外观类将客户端与子系统的内部复杂性隔离开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
外观模式的目的在于降低系统的复杂程度,很大程度上提高了客户端使用的便捷性,使得客户端无需关心子系统的工作细节,通过外观对象即可调用相关功能。
外观模式的实现关键代码:是在客户端与复杂系统之间再加一层,在这一层处理好调用顺序,依赖关系等。
模式优缺点
优点
- 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
- 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
- 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
- 可以选择性地暴露方法。子系统中定义的方式可分为两部分,一部分是给子系统外部使用,一部分是子系统内部相互调用。有了 Facade 类,就可以只暴露给外部使用的方法。
缺点
不能很好地限制客户使用子系统类,很容易带来未知风险。
最大缺点是违背了开闭原则,当增加或移除子系统时需要修改外观类。
可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。
模式结构
外观(Facade)模式包含以下主要角色。
- 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
- 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
- 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
结构图与代码
完整版
结构图
示例代码
子系统:SubSystemA,SubSystemB,SubSystemC
1 | public class SubSystemA { |
门面对象:门面对象提供一个对外的统一接口,在接口中调用多个子系统
1 | public class Facade { |
客户端:只与门面对象通信
1 | public class Client { |
抽象版
当有两个不同的高层模块需要访问子系统,已存在的门面对象不满足时,可以增加门面对象,新门面对象内还可以委托给已存在的门面对象来处理。
结构图
示例代码
1 | public class Facade2 { |
注意事项
在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,意味着它是一个单例类。在很多情况下,为了节约系统资源,一般将外观设计为单例类。
但这不意味着整个系统只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类负责和一些特定的子系统交互,向客户端提供相应的业务功能。
当一个门面对象非常庞大时,虽然门面对象都是非常简单的委托操作,也建议拆分成多个门面,方便以后的维护和扩展,通常会按功能来拆分。
门面模式目的是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,不能通过外观类来实现新的行为,不能出现具体的业务逻辑代码。
在门面模式中,门面角色是稳定的,它是一个系统对象的接口,不应该经常变化。
应用场景
- 为一个复杂系统提供一个给外部访问的简单接口。
- 当客户端与多个子系统之间存在很大的依赖性时,引入外观模式将客户端与子系统解耦,提高子系统的独立性。
- 在分层结构系统构建时,引入外观模式定义系统中每层入口点,层与层之间通过外观建立联系,降低层之间的耦合度。
典型应用
StandardSessionFacade
StandardSessionFacade 是 StandardSession 的外观对象,都实现了 HttpSession 接口。
客户端通过 HttpServletRequest.getSession()
获取 Session 对象,实际拿到的是 StandardSessionFacade。
1 | HttpSession session = httpServletRequest.getSession(); |
StandardSessionFacade 是 StandardSession 的外观对象。通过外观对象与外部进行联系,外观对象对外提供统一的访问接口,这样就屏蔽了 StandardSession 只给内部使用的部分接口,HttpSession 在这里是一个 抽象外观 。
1 | public class StandardSession implements HttpSession, Session, Serializable { |
StandardSessionFacade 构造方法依赖了 HttpSession(StandardSession),实现的 HttpSession 接口的方法委托给 HttpSession 对象执行。
类图结构如下:
基本的代码结构如下:
Session:获取 Session
1 | public interface Session { |
HttpSession:外观抽象,提供对外统一的接口
1 | public interface HttpSession { |
StandardSession:具体业务
1 | public class StandardSession implements HttpSession, Session { |
StandardSessionFacade:外观抽象
1 | public class StandardSessionFacade implements HttpSession { |
RequestFacade
HttpServletRequest 实际拿到的是 Request 的外观对象 RequestFacade。
外观对象 RequestFacade 的构造方法依赖了 Request 对象。Request 和 RequestFacade 都实现了 HttpServletRequest 接口中的方法,但外观对象 RequestFacade 中的方法是委托给 Request 对象执行的。
ApplicationContextFacade
Tomcat 的应用上下文 ApplicationContext 实现了 ServletContext,同样的要获取 ServletContext 上下文,实际拿到的是 ApplicationContext 的外观对象 ApplicationContextFacade。
相关参考
设计模式:外观模式(Facade)
http://blog.gxitsky.com/2021/06/16/DesignPatterns-17-Facade/