前言
前面已经完成了结构型模式中:适配器模式、桥接模式、装饰模式、组合模式
适配器模式是通适配器类将一个客户无法使用的类转换成客户需要的类(充电器:220V->5V)
桥接模式是定义一个“桥梁”将一个事物的多种变化通过聚合方式组合起来,简化了需要的类(画画的多种颜色与画笔的大小型号)
装饰模式是动态的给被装饰者添加功能(给咖啡加糖、牛奶)
组合模式是通过继承同一抽象类,将叶子节点与上层节点统一层次,以一种递归的方式表现“整体-部分”的关系(学校 -> 学院 - > 系)
前面四种比较复杂,这次的外观模式挺简单的,前置知识点是单例模式
现实中的问题
一个家庭影院:有屏幕、投影仪、DVD…
在程序中实现,投影仪有开启、关闭功能;屏幕有上升、下降功能;DVD有开启、关闭、暂停功能
如果通过依赖的方法将这些类依赖的客户类中,客户想看电影操作会很繁琐
(类似与每个仪器都有遥控器,你需要点击每一个遥控器才能控制仪器)
为了解决这个问题,有了外观模式
外观模式
外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式
外观模式的角色:
- Facade: 外观角色
- SubSystem:子系统角色
- Client:客户角色
实现家庭影院
每一个仪器可以普通类,当然推荐是单例模式,毕竟已经有外观类控制这些仪器了,就没必要在让其他地方获得这些仪器的控制器(类似与每个仪器控制功能都是唯一的,集合到一个控制器上,控制器只有唯一的一个)
package com.company.Structural.Facade;
//屏幕
class Screen{
private static Screen screen = new Screen();
private Screen(){};
public static Screen getScreen() {
return screen;
}
//屏幕上升
public void up(){
System.out.println(" Screen up ");
}
//屏幕下降
public void down(){
System.out.println(" Screen down ");
}
}
//投影仪
class Projector{
private static Projector projector = new Projector();
private Projector(){};
public static Projector getProjector() {
return projector;
}
//投影仪开启
public void on(){
System.out.println(" Projector on ");
}
//投影仪关闭
public void off(){
System.out.println(" Projector off ");
}
}
//DVD
class DVD{
private static DVD dvd = new DVD();
private DVD(){};
public static DVD getDVD() {
return dvd;
}
//DVD开启
public void play(){
System.out.println(" DVD play ");
}
//DVD暂停播放
public void pasue(){
System.out.println(" DVD pasue ");
}
//DVD关闭
public void off(){
System.out.println(" DVD off ");
}
}
//家庭影院
class HomeCinema{
private Screen screen ;
private Projector projector ;
private DVD dvd ;
private static HomeCinema homeCinema = new HomeCinema();
private HomeCinema() {
this.screen = Screen.getScreen();
this.projector = Projector.getProjector();
this.dvd = DVD.getDVD();
}
public static HomeCinema getHomeCinema(){
return homeCinema;
}
public void start(){
System.out.println("---------start---------------");
screen.down();
projector.on();
dvd.play();
System.out.println("-----------------------------");
}
public void pause(){
System.out.println("---------pause---------------");
dvd.pasue();
System.out.println("-----------------------------");
}
public void end(){
System.out.println("---------end---------------");
dvd.off();
projector.off();
screen.up();
System.out.println("-----------------------------");
}
}
//家庭影院用户
public class Client {
public static void main(String[] args) {
HomeCinema homeCinema = HomeCinema.getHomeCinema();
homeCinema.start();
homeCinema.pause();
homeCinema.end();
}
}
通过外观角色:控制器的三个方法,就可以完成繁琐的各个仪器的控制
外观模式的优缺点
外观模式的优点
- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可
- 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象
- 很好的实现了迪米特法则:客户类与子系统并无交流
通过外观角色,隔离开了客户类和子系统,并在外观角色中设置好子系统的调用,客户类中的使用简单明了
外观模式的缺点
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
因为子系统的功能调用全部在外观角色中写死了,修改时需要改动外观角色
使用场景
通过上面的使用和优缺点的分析,应该可以明白外观模式适用的场景:
- 当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统
- 客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度
模式扩展
外观模式思考
- 一个系统可以有多个外观角色
我们把子系统、外观角色设置称为单例模式,但是系统中可以有多个外观角色,如果客户有需求操作特定的子系统,就可以设置多个外观角色(但如果需要过多这不是最好的方法) - 不要试图通过外观类为子系统增加新行为,外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现
抽象外观角色
前面说了,外观模式的缺点是外观角色内对子系统的调用已经写死了,每一次修改都需要改动外观角色类,违背了开闭原则
解决方法就是新增一个抽象外观角色(类似与抽象工厂模式对工厂方法模式的改进)
对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的
总结
- 外观模式相对简单,是对迪米特法则的应用,即通过外观角色隔离开客户类和子系统,为子系统提供一个集中化和简化的沟通渠道,使得客户类变的简单
- 外观模式推荐使用单例模式构建子系统和外观角色,节省系统资源
- 外观模式的优点:应用迪米特法则,实现了子系统与客户之间的松耦合关系,简化了客户类;外观模式的缺点:不能很好的现在客户类使用子系统类,且当增加子系统时需要修改外观角色,违背了开闭原则
- 添加一个抽象外观角色来管理外观角色,可以根据客户的需要增加新的外观角色