外观模式,也叫作门面模式,是使用频率非常高的七个结构型设计模式之一,它是迪米特法则的具体实现,目标为降低原有系统复杂度的同时,降低业务场景类与其他各个系统的耦合性。其实对于面向对象有基础的我们,即使我们第一次听说外观模式,也有可能在开发中无意的就是用它了,它是依赖倒转原则以及迪米特法则的完美体现,关于关于迪米特法则和依赖倒转原则您可以参考老四写的《浅析设计模式第十一章之迪米特法则》和《浅析设计模式第五章之依赖倒转原则》。至于七个结构型设计模式请见如下列表(不出意外的话,老四会壹壹浅析):
- 适配器模式
- 桥接模式
- 组合模式
- 装饰模式
- 外观模式
- 享元模式
- 代理模式
定义
外观模式(Facade): 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式包含两个角色:
- Facade(外观角色): 在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
- SubSystem(子系统角色): 在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
在这样的设计之下,我们无论是对子系统的增删改都不影响客户端(极少影响客户端的修改),一切的修改直接在中转的外观类中完成即可。所以看得出来,外观模式并不完全遵从开放-封闭原则,关于开闭原则您可以参考《浅析设计模式第四章之开放-封闭原则》。开发初期,我们时刻要有意识的将不同的业务层次分隔,随着开发的深入,子系统会随着重构演化而变得越来越复杂,所以分层结构的框架设计越来越变得流行,这也是外观模式的体现。接下来我们使用代码来实现一下外观模式的具体使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.glorze.facade; /** * 灯泡类 * @ClassName Light * @author: glorze.com * @since: 2018年7月1日 下午1:17:41 */ public class Light { private String position; public Light(String position) { this.position=position; } public void on() { System.out.println(this.position + "灯打开!"); } public void off() { System.out.println(this.position + "灯关闭!"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.glorze.facade; /** * 风扇类 * @ClassName Fan * @author: glorze.com * @since: 2018年7月1日 下午1:18:43 */ public class Fan { public void on() { System.out.println("风扇打开!"); } public void off() { System.out.println("风扇关闭!"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.glorze.facade; /** * 空调类 * @ClassName AirConditioner * @author: glorze.com * @since: 2018年7月1日 下午1:18:31 */ public class AirConditioner { public void on() { System.out.println("空调打开!"); } public void off() { System.out.println("空调关闭!"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.glorze.facade; /** * 电视类 * @ClassName Television * @author: glorze.com * @since: 2018年7月1日 下午1:19:54 */ public class Television { public void on() { System.out.println("电视机打开!"); } public void off() { System.out.println("电视机关闭!"); } } |
以上四个类就是我们定义的四个电器类,属于子系统角色,他们完全可以单独被客户端分别顺序调用,例如客户端现在要同时打开或者关闭以上四个电器,如果直接调用的话,在客户端我们需要分别实例化这四个对象,并且执行对应电器类的启动或者关闭方法,造成了业务场景与系统类耦合性过高,还导致客户端变成了业务复杂的大类,所以我们设计一个GeneralSwitchFacade通用开关的外观类,在这个第三方中转类中,我们封装四个电器各种各样的变化,然后供客户端调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package com.glorze.facade; /** * 电器开关外观类 * @ClassName GeneralSwitchFacade * @author: glorze.com * @since: 2018年7月1日 下午1:19:14 */ public class GeneralSwitchFacade { private Light[] lights=new Light[4]; private Fan fan; private AirConditioner ac; private Television tv; public GeneralSwitchFacade() { lights[0]=new Light("左前"); lights[1]=new Light("右前"); lights[2]=new Light("左后"); lights[3]=new Light("右后"); fan=new Fan(); ac=new AirConditioner(); tv=new Television(); } public void on() { lights[0].on(); lights[1].on(); lights[2].on(); lights[3].on(); fan.on(); ac.on(); tv.on(); } public void off() { lights[0].off(); lights[1].off(); lights[2].off(); lights[3].off(); fan.off(); ac.off(); tv.off(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.glorze.facade; /** * 客户端 * @ClassName Client * @author: glorze.com * @since: 2018年7月1日 下午1:20:01 */ public class Client { public static void main(String args[]) { // 只需要调用外观类 GeneralSwitchFacade gsf=new GeneralSwitchFacade(); gsf.on(); System.out.println("================"); gsf.off(); } } |
输出结果如下:
左前灯打开!
右前灯打开!
左后灯打开!
右后灯打开!
风扇打开!
空调打开!
电视机打开!
================
左前灯关闭!
右前灯关闭!
左后灯关闭!
右后灯关闭!
风扇关闭!
空调关闭!
电视机关闭!
你看这样客户只需要负责跟外观角色说自己需要什么,剩下的所有事情都有GeneralSwitchFacade类来处理,客户端不与具体的电器类进行直接的交互操作,将本身复杂的电器系统变得简单起来。但是老四之前提及到过,这样的设计模式不符合开放封闭原则,当我们需要新增一个电器类或者删除一个电器类的时候,我们都需要去修改GeneralSwitchFacade这样的外观类,甚至是客户端的调用源码,因此根据开放封闭的原则,在具体外观类的基础上,我们可以通过引入抽象外观类,对于新增的外观角色我们只需要继承抽象的外观类从而实现新的业务需求,代码想必就不需要老四来亲自演示了。
外观/门面模式的优缺点总结:
优点
- 对客户端屏蔽子系统,封装子系统,是客户端调用方便简单,减少客户端与子系统的关联,降低耦合。
- 子系统的修改保持独立,某个子系统的修改不影响其他系统。这也是分层设计的体现。
缺点
- 对于限制客户端与子系统的交互并不完美,同时所有的业务逻辑全被外观类封装的话会影响系统的可变性以及灵活性。幸好外观模式不影响客户端可以直接调用子系统。
- 违背开放-封闭原则
适用场景
- 为新系统开发一个外观Facade类,来提供涉及粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
- 层次化框架、结构,关于应用分层可以参考一下老四浅析的《阿里巴巴Java开发手册第六章-应用分层篇》。
- 客户端与子系统有很大的依赖性,这个时候可以考虑Facade外观类来做第三方中转。
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请赞助盒烟钱,点我去赞助。抱拳。