外观模式
1 实际场景
1.1 代码模板生成
很多平台代码工具可以根据配置生成相应的模板代码,用户则只需要关注特定的业务功能实现,降低用户使用的复杂度以及提高效率。
假设生成的模板代码都具有基本的三层架构,即:表现层、逻辑层和数据层,所以代码生成工具应该具有相应代码生成的处理模块,另外代码工具还需要有相应的配置管理模块,定义配置模板,用来决定生成代码的逻辑。
如何实现该代码生成工具?
1.2 非设计模式的解决方案
非常 straightforward
的实现方式,方便示例,其中每个模块的实现就简单示意。
1、 配置管理模块的配置对象定义
1 | /** |
2、配置管理定义
1 | /** |
3、各个模块的生成代码,通过获取配置文件的内容,按照配置生成相应的代码
1 | /** |
4、客户端实现
1 | /** |
1. 3 有何问题
如上述案例代码,客户端为了使用生成代码工具,需要与生成代码的内部多个模块交互。从用户的角度来说,代码生成工具的使用体验非常差,若工具内部某个模块发生变化,还需要引起客户端也随之变化。如何处理此问题?
2 外观模式
2.1 定义
外观模式(Facade Pattern
)属于结构型模式。为子系统中的一组接口提供一致的界面,Facade
模式定义了一个高层接口,此接口可以使子系统更加容易使用。
此处的的界面指的是从一个组件外部来看这个组件,对外提供的接口,就是这个组件的界面,即:外观。
2.2 外观模式解决问题思路
上面案例中,用户需要更简单的操作,此时需要为客户端定义一个简单的接口,客户端调用这个接口就行。
此处提到的接口,不是指的是
interface
,在外观模式中,一般指的是外观类,在这个类中定义客户端需要的方法,然后在方法实现里面,有外观类去分别调用多个内部模块来实现功能。
2.3 结构说明
- Facade:定义子系统的多个模块对外的高层接口。需要调用内部多个模块,从而把客户的请求代理给适当的子系统的系统对象;
- 模块:接受
Facade
对象的委派,实现具体的功能,各个模块之间可能有交互。其中Facade
对象能感知各个模块,但是各个模块无法感知Facade
对象的存在;
2.4 示例代码
1、内部多个模块的接口定义(可以是任意的功能方法)
1 | /** |
2、模块接口的实现
1 | /** |
3、定义外观对象
1 | /** |
4、客户端,直接使用外观对象
1 | /** |
2.5 重写方案
1、重写的方案中,子模块的 Business
、DAO
、Presentation
都是同上定义一致,需要添加一个 Facade
对象。
1 | /** |
2、客户端实现,不需要在与多个子系统内部模块交互,直接使用外观对象
1 | /** |
Facade
类也被称为子模块模块对外的接口,有了 Facade
类,客户端就不需要知道系统内部的实现细节,甚至客户端都不需要知道内部模块的存在,客户端只需要同 Facade
类交互就行,从而更好的实现了客户端和子系统中的模块的解耦,让客户端更容易的使用系统。
3 模式讲解
3.1 认识外观模式
3.1.1 目的
外观模式的目的是让外部减少与子系统内多个模块的交互,松耦合,从而让外部能更简单地使用子系统。
值得注意的是,虽然在代码层面可以在外观类中定义额外的与子系统无关的功能方法,但是不建议这么做。外观应该包装已有的功能,它主要负责组合已有功能来实现客户需要,而不是添加新的实现。
3.1.2 外观模式提供缺省的功能实现
随着业务的发展,一般系统会越做越大、越来越复杂,随着代码的要求也就更高。为了提高系统的可重用性,通常会把一个大的系统分成很多个子系统,再把一个子系统分成很多更小的子系统,一直分下去,分到一个一个小的模块,这样一来,子系统的重用性会得到加强,也更容易对子系统进行定制和使用。
但是随着带来一个问题,如果用户不需要对子系统进行定制,仅仅就是想要使用它们来完成一定的功能,那么使用起来会比较麻烦,需要跟这多个模块交互。外观对象就可以为用户提供一个简单的、缺省的实现,这个实现满足了大多数的用户的需求。但是外观并不限制那些需要更多定制功能的用户,直接越过外观去访问内部的模块的功能。
3.2 实现方式
3.2.1 单例实现
对于一个子系统而言,外观类不需要很多,通常可以实现成为一个单例。也可以直接把外观中的方法实现成为静态的方法,这样就可以不需要创建外观对象的实例而直接就可以调用,这种实现相当于把外观类当成一个辅助工具类实现。简要的示例代码如下:
1 | public class Facade { |
3.2.2 接口实现
虽然 Facade
通常直接实现成为类,但是也可以把 Facade
实现成为真正的 interface
,只是这样会增加系统的复杂程度,因为这样会需要一个 Facade
的实现,还需要一个来获取 Facade
接口对象的工厂。
此时客户端需要先从工厂获取 Facade
的接口,才能调用对应功能。
接口实现的好处
好处:就是能够有选择性的暴露接口方法,尽量减少模块对子系统外提供的接口方法。即:一个模块的接口里面定义的方法可以分成两部分,一部分是给子系统外部使用的,一部分是子系统内部的模块间相互调用时使用的。有了 Facade
接口,则用于子系统内部的接口功能就不用暴露给子系统外部。
示例:
定义如下的 A、B、C 模块的接口:
1 | public interface AModuleApi { |
Facade
接口:
1 | public interface FacadeApi { |
在示例中,外部只需要有 Facade
接口,就不再需要其它的接口,这样就能有效地屏蔽内部的细节,可以避免客户端去调用 A 模块的接口时,发现了一些不需要它知道的接口,这会造成 “接口污染”。
3.2.3 Facade 的方法实现
Facade
的方法实现中,一般是负责把客户端的请求转发给子系统内部的各个模块进行处理,Facade
的方法本身并不进行功能的处理,Facade
的方法的实现只是实现一个功能的组合调用。
但是 Facade
并不禁止在其中实现其他逻辑,但是不符合 Facade
的设计模式本意。
3.3 外观模式的优缺点
优点
松散耦合
外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。如果调用模块的算法发生了变化,比如变化成要先调用
B
,然后调用A
,那么只需要修改Facade
的实现就可以。简单易用
外观模式使子系统更加易用,客户端不需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以,相当于外观类为外部客户端使用子系统提供了一站式服务。
还有一个潜在的优点,对使用
Facade
的用户来说,Facade
大大节省了用户的学习成本,他们只需要了解Facade
的用法即可,无需再深入到子系统内部,去了解每个模块的细节,也不用和这多个模块交互,从而使得开发简单,学习也容易。
缺点
增加系统复杂度
Facade
过多的时候(可能有些不合理的设计),容易导致用户比较困惑,何时选择调用Facade
,何时选择调用内部模块。
3.4 思考
3.4.1 本质
封装交互,简化调用
3.4.2 设计原则
最少知识原则
用户无需了解系统内部实现细节,只需要了解外观类就行。且通过外观模式可以在不影响用户的情况下,对系统内部模块进行维护和扩展。
3.4.3 使用场景
- 若需要为一个复杂系统提供一个简单接口时;
- 若需要客户端程序与抽象类实现松散耦合,通过外观对象将子系统与客户端分离,同时也能提高系统子模块的独立性和可移植性;
- 构建多层结构系统时,通过外观对象作为每层的入口,可以简化系统层间调用,同时也松散了层次之间的依赖关系;
3.5 相关模式
3.5.1 外观模式和中介者模式
- 外观模式封装的是单方面的请求,指的是从客户端对系统的调用;
- 中介者模式主要用来封装多个对象之间相互的交互,主要是在系统内部的多个模块之间的交互;
- 外观模式一般是组合调用或者是对内部系统的子模块调用,在外观类中不实现这些功能;
- 中介者模式中需要实现具体具体的交互功能;
- 外观模式的目的是简化客户端的调用,不需要了解内部实现细节;
- 中介者模式的目的是松散多个模块之间的耦合,具体的耦合关系在中介者中实现;
3.5.2 外观模式和单例模式
在实际的生产场景中,外观模式与单例模式组合使用,将 Facade
类实现为单例,节省系统资源。
3.5.3 外观模式和抽象工厂模式
外观模式需要与系统内部的多个模块交互,组合这些接口能力来完成客户需要的功能,其中每个模块都有自己定义的接口,这些接口的则可以使用抽象工厂来实现,将模块内部的实现对 Facade
进行屏蔽,即,Facade
只需要从模块中获取需要对应功能的接口,无需深入内部的实现细节。