外观模式
在开文之前,我想问一下各位,对于吃饭,您觉得是去下馆子方便还是自己在家里弄方便呢?我个人的话就肯定是觉得下馆子方便,当然毛爷爷允许我这么做的话。
如果自己在家里做饭呢,我们要准备锅,准备煤气,还要照顾家里人的口味去针对性准备食材和调味品。而下馆子呢,只要我们跟饭店服务员说一声,我想吃的是什么菜,不一会儿菜品便盛到您面前,饭店帮我们省去了一大串的复杂环节,我们所要做的,就只是跟服务员点个菜而已。
这种情况在软件开发中也屡见不鲜,有时一个客户类需要和多个业务类交互,这些业务类往往是一起出现的。由于涉及的类数量较多,导致编写代码时代码被拧成一坨,及其不雅和难以维护。
针对这种情况,我们会非常需要在使用这些交互的业务类时,由一个统一的调度者来帮我们调用,就如服务员,而客户端,则只需要使用这个调度者即可。在设计模式中,我们将此种方式称之为外观模式。外观模式就是将那一大坨代码也就是多个业务类的调用给整合到一起,然后给客户端提供一个统一的入口。在外观模式中,这一坨代码有时也被称之为子系统。这里不要混淆概念,子系统只是一种在结构上的叫法,一个类,一个模块,一个系统都可以称之为子系统。
它给我们带来的好处是咋样的呢,往下看!
就比如:
使用了外观模式后:
诚如上图,外观模式让系统的结构更加清晰和解耦,通过一个统一的依赖来替换多个类与多个类之间这种复杂的依赖关系。
Facade Pattern: Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to user.
外观模式:为处于子系统中的一组接口提供一个统一的接口。外观类定义了一个更高级别的接口来让子系统更为容易的被使用。
外观模式设计的角色
- ① 外观类:客户端可以直接调用它。它依赖了一系列类,并且知道每个类的职责。在正常情况下,它会将请求针对性的转发给相应的子系统进行处理。
- ② 子系统类:每个子系统可能是一系列类的集合,也可能是具有某项职责的单个类。客户端可以直接调用它,也可以通过外观类去调用它们。子系统类并不知道有外观类的存在,对于它们而已,外观类就是一个“客户端”。
典型的外观模式结构
Facade与多个子系统进行交互,客户端直接调用Facade。多个子系统的合并让Facade在逻辑上的层级更高。
没有相应的抽象化,当对代码进行维护时可能需要修改到旧代码,不符合开闭原则。
典型代码如下:
抽象外观类结构
针对典型的外观模式结构的缺点,对外观类增加了一个抽象,在对外观类进行扩展维护时,针对抽象接口进行编程,按需引用新的子系统去调整代码结构。具备良好的可扩展性,不违背开闭原则。
典型代码如下:
代码分析
假设现在有这么一个需求,我需要把一个entity或者把一个entity集合转化为json后存入文件中。
方案一
测试用的entity
自定义json转换类
文件写入类
外观类
测试结果:
方案一直接新建了一个TransJson类和FileHelper类,TransJson类负责将对象转换成json,FileHelper类则负责将转换后的json写入文件。TransAndStoreFacade类引用了这两个对象后对客户端提供了transObjAndStore(Object obj, String path)和transArrAndStore(Collection objs, String path)两个方法,客户端只需调用外观类的方法就可以完成将对象转换成json并且存储的工作。
但是此种编码方式有个问题就是当你想换一种json转换的api,那么你就不得不去修改源代码,因而衍生除了方案二来解决这个问题。
方案二
采用抽象外观类的编码方法,引入Gson替代自定义的TransJson类。
类图如下:
抽象外观类
利用Gson替代自定义TransJson类后的新外观类
实现了抽象外观接口的旧外观类
通过引入抽象外观类,解决了方案一存在的违背开闭原则的问题。当然,如果还想更换json转换工具或者文件写入类,则只需要新增一个实现了抽象外观接口的类后引用新的取代对象并且编写接口方法即可。
到这里,将案例代码结合上方的两种外观模式结构的论述,我相信是可以对两种外观模式有个浅显的认知的。
外观模式的优缺点与适用场景
适用场景:
- 客户端与多个子系统存在较大的依赖,可以通过引入外观模式将客户端与子系统解耦。
- 构建一些层次化结构时可以利用外观模式为每一层提供一个统一的接口,将层与层间解耦。
- 当要为访问一系列复杂的子系统提供一个统一的简单入口。
优点:
- 减少了客户端需要直接交互的子系统数量,使子系统的使用变的更为便捷。
- 当子系统发生变动时并不会影响到客户端,只需要调整外观类即可。
- 子系统与子系统间隔离,当子系统内部发生变动时并不会影响到外观类。
缺点:
- 增加了外观类,请求需要转发,增加了时间和空间成本。
- 设计不当会违背开闭原则,接比如案例中的方案一。
- 子系统依旧可以直接提供服务,外观类并不能限制客户端直接使用子系统,只能说它为客户端提供了一个更便捷的操作而已,并且外观类也不能对客户端直接访问子系统做太多的限制,不然就丧失了灵活性。