设计模式之外观模式

外观模式又叫门面模式,属于结构型模式;是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

现在微服务和模块化越来越流行,我们都会把一个复杂的系统划分成几个较小的子系统。但是这样做了以后,系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。

外观模式的用意是为子系统提供一个集中化和简化的沟通管道,而不能向子系统加入新的行为。如果一个外观模式不能将子系统所有的行为提供给外界,那么可以通过修改门面类或者继承门面类的办法,使门面类或其子类能够将子系统的行为提供给外界。但是,如果一个子系统没有某个行为,想通过修改门面类或者继承门面类的办法来提供这个新的行为是错误的。

不使用外观模式

一个保安系统由两个录像机、三个电灯、一个遥感器和一个警报器组成。保安系统的操作人员需要经常将这些仪器启动和关闭。首先,在不使用门面模式的情况下,操作这个保安系统的操作员必须直接操作所有的这些部件。

不适用外观模式的UML类图如下:

如上如所示,Client对象需要引用到所有的录像机(Camera),电灯(Light),感应器(Sensor)和警报器(Alarm)对象。client对象必须对保安系统全知全能,如果系统引入了一个新的对象,那需要修改的地方就很多了。

录像机:

package com.charon.facade;

/**
 * @className: Camera
 * @description: 录像机系统
 * @author: charon
 * @create: 2022-03-22 22:27
 */
public class Camera {

    /**
     * 打开录像机
     */
    public void turnOn(){
        System.out.println("录像机开机。。。。");
    }

    /**
     * 关闭录像机
     */
    public void turnOff(){
        System.out.println("录像机关机。。。。");
    }

    /**
     * 转动录像机
     */
    public void rotate(int degress){
        System.out.println("录像机转动 " + degress + "度");
    }
}

电灯:

package com.charon.facade;

/**
 * @className: Light
 * @description: 灯泡
 * @author: charon
 * @create: 2022-03-22 22:30
 */
public class Light {

    /**
     * 打开灯泡
     */
    public void turnOn(){
        System.out.println("灯泡打开。。。。");
    }

    /**
     * 关闭灯泡
     */
    public void turnOff(){
        System.out.println("灯泡关掉。。。。");
    }

    /**
     * 换灯泡
     */
    public void changBulb(){
        System.out.println("换了一个灯泡");
    }
}

感应器:

package com.charon.facade;

/**
 * @className: Sensor
 * @description: 感应器
 * @author: charon
 * @create: 2022-03-22 22:31
 */
public class Sensor {

    /**
     * 打开感应器
     */
    public void activate(){
        System.out.println("感应器打开。。。。");
    }

    /**
     * 关闭感应器
     */
    public void deactivate(){
        System.out.println("感应器关掉。。。。");
    }

    /**
     * 触发感应器
     */
    public void trigger(){
        System.out.println("感应器被触发了。。。");
    }
}

警报器:

package com.charon.facade;

/**
 * @className: Alarm
 * @description: 警报器
 * @author: charon
 * @create: 2022-03-22 22:33
 */
public class Alarm {
    /**
     * 打开警报器
     */
    public void activate(){
        System.out.println("警报器打开。。。。");
    }

    /**
     * 关闭警报器
     */
    public void deactivate(){
        System.out.println("警报器关掉。。。。");
    }

    /**
     * 拉响警报器
     */
    public void ring(){
        System.out.println("警报器被拉响了。。。");
    }

    /**
     * 停掉警报器
     */
    public void stopRing(){
        System.out.println("警报器被关停了。。。");
    }
}

测试:

package com.charon.facade;

/**
 * @className: Client
 * @description: 外观模式
 * @author: charon
 * @create: 2022-03-21 22:30
 */
public class Client {

    private static Camera camera1 = new Camera(), camera2 = new Camera();

    private static Light light1 = new Light(), light2 = new Light(), light3 = new Light();

    private static Sensor sensor = new Sensor();

    private static Alarm alarm = new Alarm();

    public static void main(String[] args) {
        camera1.turnOn();
        camera2.turnOn();
        light1.turnOn();
        light2.turnOn();
        light3.turnOn();
        sensor.activate();
        alarm.activate();

        light1.changBulb();
        camera1.rotate(180);
        sensor.trigger();
        alarm.ring();
    }
}

打印:
    录像机开机。。。。
    录像机开机。。。。
    灯泡打开。。。。
    灯泡打开。。。。
    灯泡打开。。。。
    感应器打开。。。。
    警报器打开。。。。
    换了一个灯泡
    录像机转动 180度
    感应器被触发了。。。
    警报器被拉响了。。。

使用外观模式

使用外观模式的UML类图如下:

在外观模式中,就只涉及到门面角色和子系统角色两种角色了:

  • 门面(Facade)角色:客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个〉子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
  • 子系统(Subsystem)角色:可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。

门面类:

package com.charon.facade;

/**
 * @className: SecurityFacade
 * @description: 门面角色
 * @author: charon
 * @create: 2022-03-22 23:02
 */
public class SecurityFacade {

    private static Camera camera1 = new Camera(), camera2 = new Camera();

    private static Light light1 = new Light(), light2 = new Light(), light3 = new Light();

    private static Sensor sensor = new Sensor();

    private static Alarm alarm = new Alarm();

    /**
     * 系统启动
     */
    public void activate(){
        camera1.turnOn();
        camera2.turnOn();
        light1.turnOn();
        light2.turnOn();
        light3.turnOn();
        sensor.activate();
        alarm.activate();
    }

    /**
     * 系统关闭
     */
    public void deactivate(){
        camera1.turnOff();
        camera2.turnOff();
        light1.turnOff();
        light2.turnOff();
        light3.turnOff();
        sensor.deactivate();
        alarm.deactivate();
    }
}

测试:

package com.charon.facade;

/**
 * @className: Client
 * @description: 外观模式: http://c.biancheng.net/view/1369.html
 * @author: charon
 * @create: 2022-03-21 22:30
 */
public class Client {

    private static SecurityFacade facade = new SecurityFacade();

    public static void main(String[] args) {
        facade.activate();
        facade.deactivate();
    }
}

打印:
    录像机开机。。。。
    录像机开机。。。。
    灯泡打开。。。。
    灯泡打开。。。。
    灯泡打开。。。。
    感应器打开。。。。
    警报器打开。。。。
    录像机关机。。。。
    录像机关机。。。。
    灯泡关掉。。。。
    灯泡关掉。。。。
    灯泡关掉。。。。
    感应器关掉。。。。
    警报器关掉。。。。

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点:

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

外观(Facade)模式的主要缺点如下:

  1. 不能很好地限制客户使用子系统类,很容易带来未知风险。
  2. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

外观模式的应用场景

通常在以下情况下可以考虑使用外观模式。

  1. 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  2. 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  3. 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
posted @ 2022-03-22 23:15  pluto_charon  阅读(202)  评论(0编辑  收藏  举报