GoF设计模式:Facade Pattern 外观模式

Posted ProjectDaedalus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GoF设计模式:Facade Pattern 外观模式相关的知识,希望对你有一定的参考价值。


之前我们介绍了Adapter Pattern适配器模式,通过适配器实现两个不兼容类的配合工作。而这里,我们将会介绍另外一种结构型的设计模式——Facade Pattern外观模式。有些同志会将这二者(适配器模式、外观模式)混淆,但其二者目的有着明显的区别,前者是通过包装来改变接口以达到适配的目的;后者则是通过包装简化调用、降低耦合度

GoF设计模式(八):Facade Pattern 外观模式

abstract.jpeg

模式思想

在介绍该模式之前,我们先来看一个生活中的案例。当你下班回家后,需要把家里的电器一个一个依次打开,开电视、开空调、开灯……,而当你准备出门上班时,又要把之前打开的电器一个一个依次关掉,关电视、关空调、关灯……。这样家里电器一多以后,每次开开关关是很麻烦的。那么我们就会想如果有一个万能开关,可以直接一次性控制家中全部电器的开关。这样是不是就方便很多了?每次回家或者上班出门,只需按一下万能开关就可以完成之前的多次开关操作了

利用我们在现实世界获得的指引,将其运用于软件工程领域下,即提炼为我们今天所要介绍的设计模式——Facace Pattern外观模式,其又被称作为门面模式。在这个例子中,家中的各个电器可以称之为子系统,而这个万能开关就是外观模式中的外观角色。很多时候client不应该需要了解各个子系统的工作流程,否则会造成client与各子系统之间的高度耦合。那么我们就可以通过一个外观角色,其负责将子系统及工作流程进行封装以简化client的使用。在Facade Pattern外观模式中,涉及以下两个角色

  • 子系统角色:通常来说在一个系统中会有若干个子系统(比如本文例子中的各个家用电器)。client即可以直接调用各个子系统,也可以通过外观角色来实现对各子系统的调用
  • 外观角色:其通过将各子系统的方法调用逻辑、顺序的有机组合、封装。大大简化了client使用各子系统的流程及难度,降低了client与各子系统之间的耦合度

实践

现在我们通过Java实现上文提到的生活例子。首先定义实现家里各个子系统——各电器类

/**
 * 子系统:电视
 */

public class Tv {
    /**
     * 打开电视
     */

    public void turnOn() {
        System.out.println("打开电视 ... ");
    }

    /**
     * 关闭电视
     */

    public void turnOff() {
        System.out.println("关闭电视");
    }
}
...
/**
 * 子系统:加湿器
 */

public class Humidifier {
    /**
     * 打开加湿器
     */

    public void turnOn() {
        System.out.println("打开加湿器 ... ");
    }

    /**
     * 关闭加湿器
     */

    public void turnOff() {
        System.out.println("关闭加湿器");
    }
}
...
/**
 * 子系统:空调
 */

public class AirConditioner {
    /**
     * 打开空调
     */

    public void turnOn() {
        System.out.println("打开空调 ... ");
    }

    /**
     * 关闭空调
     */

    public void turnOff() {
        System.out.println("关闭空调");
    }
}

由于现在我们还没引入外观模式,隔壁老王每次回到家、出门时都只好亲自操作各个家电,如下所示

/**
 * 外观模式Demo
 */

public class FacadePatternDemo {
    public static void main(String[] args) {
        System.out.println("---------------- Test 1 ----------------");
        // 不使用外观模式,客户端(老王)直接访问各子系统(各电器)
        Tv tv = new Tv();
        AirConditioner airConditioner = new AirConditioner();
        Humidifier humidifier = new Humidifier();

        System.out.println("【老王下班回到家,打开家中所有电器】");
        tv.turnOn();
        airConditioner.turnOn();
        humidifier.turnOn();

        System.out.println("【老王准备出门上班,关闭家中所有电器】");
        tv.turnOff();
        airConditioner.turnOff();
        humidifier.turnOff(); 
    }
}

从下面的结果可以看到虽然操作有点麻烦,但结果至少符合预期

figure 1.png

既然这里我们介绍了外观模式,就利用该模式来优化下吧。一般来说子系统一般已经先行存在了,那么实际上我们就只需要再引入一个外观角色——即家用电器的外观角色ElectricApplianceFacade类。可以看到,其内部持有了各个子系统对象,并将多个子系统的操作有机地进行组合、封装。据此实现client使用的方便简洁

/**
 * 家用电器的外观角色
 */

public class ElectricApplianceFacade {
    /**
     * 子系统:电视
     */

    private Tv tv;

    /**
     * 子系统:空调
     */

    private AirConditioner airConditioner;

    /**
     * 子系统:加湿器
     */

    private Humidifier humidifier;

    public ElectricApplianceFacade() {
        this.tv = new Tv();
        this.airConditioner = new AirConditioner();
        this.humidifier = new Humidifier();
    }

    /**
     * 打开所有家用电器
     */

    public void turnOnAll() {
        tv.turnOn();
        airConditioner.turnOn();
        humidifier.turnOn();
    }

    /**
     * 关闭所有家用电器
     */

    public void turnOffAll() {
        tv.turnOff();
        airConditioner.turnOff();
        humidifier.turnOff();
    }
}

好了,现在引入了外观模式,隔壁老哥再也不用一个一个的开、关家电了,而是通过外观角色就可以很方便地实现一键开关

/**
 * 外观模式Demo
 */

public class FacadePatternDemo {
    public static void main(String[] args) {
        System.out.println("---------------- Test 2 ----------------");
        // 使用外观模式,客户端(老王)通过外观角色间接访问各子系统(各电器)
        ElectricApplianceFacade electricApplianceFacade = new ElectricApplianceFacade();

        System.out.println("【老王下班回到家,打开家中所有电器】");
        electricApplianceFacade.turnOnAll();

        System.out.println("【老王准备出门上班,关闭家中所有电器】");
        electricApplianceFacade.turnOffAll();
    }
}

figure 2.png

其它

迪米特法则

迪米特法则,又称作最少知识原则。其内涵是一个对象对其它对象的了解程度应该尽可能的小,以降低彼此之间的耦合。显然我们可以看到,Facade Pattern外观模式就是迪米特法则的具体实践

应用

外观模式在第三方框架、组件中亦被大量使用。这里只提下其在Java日志框架下的应用。众所周知,Java下的日志框架非常繁多,有JDK Logging、Log4J、Log4J2、LogBack等,一旦变更项目中现有的日志框架选型,就需要进行大量相应的修改非常麻烦。为此SLF4J应运而生,其与其它日志框架不同的地方在于,其不是一个具体的日志框架,而是一个简单的日志框架的外观。在实际项目中,一般我们是通过SLF4J这个日志外观角色来使用日志,而不是直接显式操作、使用某一个具体的日志框架,以降低耦合度,也便于后期升级、变更具体日志框架的选型

参考文献

  1. Head First 设计模式 弗里曼著

以上是关于GoF设计模式:Facade Pattern 外观模式的主要内容,如果未能解决你的问题,请参考以下文章

外观模式(Facade Pattern)

10.外观模式(Facade Pattern)

设计模式之- 外观模式(Facade Pattern)

GOF设计模式(10)外观模式

外观/门面模式(Facade Pattern)

外观模式(Facade Pattern)