外观模式(Facade)是23种设计模式之一,也称为门面模式。DP中是这么定义外观模式的:

外观模式为子系统的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

一个系统都是由各种大大小小不同功能的类组成的,每一个类都会提供被外部调用的接口,我们可以通过这些接口去使用这些类,但是客户端要一个个的去调用这些类非常的麻烦,而且也会造成客户类与子系统类耦合性高的问题,就如下图:

设计模式之外观模式_第1张图片

所以为了解决这种问题,我们需要定义一个高层接口,让这个高层接口去完成各个子系统类接口的调用,客户类就只需要对这个高层接口进行调用即可,这样降低了客户类与子系统类的耦合,也能让客户端更加方便的使用这个系统,这就是外观模式,如下图:
设计模式之外观模式_第2张图片

我们再用电脑来举个简单的例子,之所以我们可以很方便的一键式的开启和关闭电脑,就是因为机箱给我们提供了一个开关按钮,这个开关按钮就相当于是一个高层接口,我们只需要知道按这个按钮就能开启电脑,背后的复杂交互我们不需要关心。如果没有这个统一的开关按钮,那么我们想要开启和关闭电脑就得对硬件一个个的去操作了,就如下图:
设计模式之外观模式_第3张图片

让我们使用简单的代码来实现这个场景:

package org.zero01.system;

public class CPU {

    public void turnOn() {
        System.out.println("CPU-开");
    }

    public void turnOff() {
        System.out.println("CPU-关");
    }
}

package org.zero01.system;

public class GPU {

    public void turnOn() {
        System.out.println("GPU-开");
    }

    public void turnOff() {
        System.out.println("GPU-关");
    }
}

package org.zero01.system;

public class Memory {

    public void turnOn() {
        System.out.println("内存-开");
    }

    public void turnOff() {
        System.out.println("内存-关");
    }
}

package org.zero01.system;

public class Disk {

    public void turnOn() {
        System.out.println("磁盘-开");
    }

    public void turnOff() {
        System.out.println("磁盘-关");
    }
}

用户代码如下:

package org.zero01.user;

import org.zero01.system.CPU;
import org.zero01.system.Disk;
import org.zero01.system.GPU;
import org.zero01.system.Memory;

public class User {

    public static void main(String[] args) {

        CPU cpu = new CPU();
        GPU gpu = new GPU();
        Memory memory = new Memory();
        Disk disk = new Disk();

        cpu.turnOn();
        gpu.turnOn();
        memory.turnOn();
        disk.turnOn();

        cpu.turnOff();
        gpu.turnOff();
        memory.turnOff();
        disk.turnOff();

    }
}

从用户调用的代码上可以看到,需要逐个去实例化各个硬件的对象,然后还得一个个的去调用开关方法很麻烦。

所以我们要利用外观模式来解决这个问题,先来看看外观模式的结构图:
设计模式之外观模式_第4张图片

使用代码实现这个简单的结构,子系统类:

class SubSystemOne{

    public void methodOne() {

        System.out.println("子系统方法一");
    }

}

class SubSystemTwo{

    public void methodTwo() {

        System.out.println("子系统方法二");

    }

}

class SubSystemThree{

    public void methodThree() {

        System.out.println("子系统方法三");

    }

}

class SubSystemFour{

    public void methodFour() {

        System.out.println("子系统方法四");

    }

}

Facade外观类:

package org.zero01.system;

/**
 * 外观类,它需要了解所有子系统类的方法或属性, 并进行处理、组合,以提供给外部调用
 */
public class Facade {

    private SubSystemOne subSystemOne;
    private SubSystemTwo subSystemTwo;
    private SubSystemThree subSystemThree;
    private SubSystemFour subSystemFour;

    public Facade() {
        subSystemOne = new SubSystemOne();
        subSystemTwo = new SubSystemTwo();
        subSystemThree = new SubSystemThree();
        subSystemFour = new SubSystemFour();
    }

    public void MethodA() {
        System.out.println("\n 方法组A() -------- ");
        subSystemOne.methodOne();
        subSystemTwo.methodTwo();
        subSystemFour.methodFour();
    }

    public void MethodB() {
        System.out.println("\n 方法组B() -------- ");
        subSystemOne.methodOne();
        subSystemTwo.methodTwo();
        subSystemThree.methodThree();
    }

}

Client客户端类:

package org.zero01.user;

import org.zero01.system.Facade;

/**
 * 由于Facade的作用,客户端可以根本不知道子系统类的存在,只需要对Facade对象进行操作即可
 */
public class Client {

    public static void main(String[] args) {

        Facade facade = new Facade();

        facade.MethodA();
        facade.MethodB();

    }
}

如上,可以看到,客户端只需要对Facade操作即可,不需要一个个的去对子系统类进行操作。这种模式用得挺多的,因为它完美的体现了依赖倒转原则以及迪米特法则的思想。

使用外观模式重构之前的代码,增加一个外观类:

package org.zero01.system;

public class MainSwitch {

    private CPU cpu;
    private GPU gpu;
    private Memory memory;
    private Disk disk;

    public MainSwitch() {
        cpu = new CPU();
        gpu = new GPU();
        memory = new Memory();
        disk = new Disk();
    }

    public void turnOn() {
        System.out.println("\n开机-------------");
        cpu.turnOn();
        gpu.turnOn();
        memory.turnOn();
        disk.turnOn();
    }

    public void turnOff() {
        System.out.println("\n关机-------------");
        cpu.turnOff();
        gpu.turnOff();
        memory.turnOff();
        disk.turnOff();
    }

}

用户类只需要对这个外观类进行操作即可,就完成了一键式开关机:

package org.zero01.user;

import org.zero01.system.MainSwitch;

public class User {

    public static void main(String[] args) {

        MainSwitch mainSwitch=new MainSwitch();

        mainSwitch.turnOn();
        mainSwitch.turnOff();

    }
}

运行结果:

开机-------------
CPU-开
GPU-开
内存-开
磁盘-开

关机-------------
CPU-关
GPU-关
内存-关
磁盘-关

何时使用外观模式:

至于何时使用外观模式,可以分为三个阶段来说:

1.首先在设计初期阶段,应该要有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问和业务逻辑层、业务逻辑层和表示层的层与层之间建立外观接口Facade,这样可以为每一层复杂的子系统提供一个简单的接口,不仅使得耦合大大降低,也让外部调用更简单。

2.其次,在开发阶段,子系统往往因为不断的重构演化而变化得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也会给外部调用它们的用户带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。

3.第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖它,此时用外观模式也是非常适合的。你可以为新系统开发一个外观Facade类,来为这个设计粗糙或高度复杂的遗留代码提供一个比较清晰简单的接口,让新系统与Facade对象交互,Facade对象则与遗留代码交互所有复杂的工作。