外观模式是一种使用频率非常高,但理解较为简单的设计模式,通过引入一个外观角色来简化客户端与子系统之间的操作,为复杂的子系统调用提供一个统一的入口,使子系统与客户端的耦合度降低,且客户端调用非常方便。
模式动机
在大多数情况下,一个网站都会提供一个网站首页。网站首页一般作为整个网站的入口,提供了通往各个子栏目的超链接,用户通过该首页即可进入子栏目获取所需信息。对于用户而言只需记住网站首页网站 URL,而无须记住每个子栏目的网址。网站首页作为用户访问子栏目的入口,是网站的外观角色。用户通过它可以方便地访问子栏目,当然也可以绕过它直接访问子栏目。
如果没有外观角色,即没有为网站提供一个首页,每个用户需要记住所有子栏目的 URL,不仅会让系统耦合度增加,用户与系统的交互也会异常复杂。
模式定义
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加易于使用。
模式结构
外观模式没有一个一般化的类图描述,这里以类图作为外观模式的描述形式之一。
-
Facade(外观角色)
在客户端可以调用这个角色的方法,在外观角色中可以知道相关子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应子系统,传递给相应的子系统对象处理。
-
SubSystem(子系统角色)
在软件系统中可以同时有一个或多个子系统角色,每一个子系统都可以被客户端直接调用,或被外观角色调用,处理外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观仅仅是另外一个客户端而言。
实例之电源总开关
一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。通过该电源总开关可以同时控制所有上述电器设备,使用外观模式设计该系统。
-
子系统类 Light
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 + "灯打开"); } }
-
子系统类 Fan
public class Fan { public void on() { System.out.println("风扇打开"); } public void off() { System.out.println("风扇关闭"); } }
-
子系统类 AirConditioner
public class AirConditioner { public void on() { System.out.println("空调打开"); } public void off() { System.out.println("空调关闭"); } }
-
子系统类 Televison
public class Television { public void on() { System.out.println("电视机打开"); } public void off() { System.out.println("电视机关闭"); } }
-
外观类 GeneralSwitchFacade
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(); } }
-
测试类 Client
public class Client { public static void main(String[] args) { GeneralSwitchFacade gsf = new GeneralSwitchFacade(); gsf.on(); System.out.println("-------------"); gsf.off(); } }
-
运行结果
模式优缺点
外观模式优点如下:
- 对客户屏蔽子系统组件,使得子系统使用起来更加方便
- 实现了子系统和客户之间的松耦合关系,子系统组件变化不会影响到客户类
- 简化了系统在不同平台之间的移植过程,因为一个子系统的修改对其他子系统没有任何影响
- 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类
外观模式缺点如下:
- 不能很好限制客户使用子系统类
- 在不引入抽象外观类的情况下,增加新的子系统可能需修改外观类和客户端的源代码,违背了开闭原则
模式适用环境
以下情况可以使用外观模式:
- 当要为一个复杂子系统提供一个简单接口时可以使用外观模式
- 客户程序与多个子系统之间存在很大依赖性
- 在层次化结构中,可以使用外观模式定义系统每一层的入口,层与层之间不直接联系,通过外观类建立联系,降低层之间的耦合度
一个系统有多个外观类
为了节约资源,一般将外观类设计为单例类,但这并不意味整个系统只有一个外观类。一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能
不要试图通过外观类为子系统增加新行为
外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现
外观模式和迪米特法则
迪米特法则要求你与直接的朋友通信。外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少。外观类充当了客户类与子系统类之间的第三者,降低了客户类与子系统之间的耦合度。外观模式是实现代码重构以达到迪米特法则要求的一个强有力的武器。
抽象外观类的引入
外观模式最大的缺点是违背了开闭原则,当增加新的子系统或移除子系统时需修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类编程。对于新的业务需求,不修改原有外观类,对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象。