一 开着小破车的快乐

不知道大家有没有这样开或者坐过这样一辆“小破车”,他能跑,但是内部娱乐或者说一些辅助的设备几乎可以忽略不计,条件虽然艰苦了一些,但是我们还是要自己给自己创造快乐 ,夏天太热了,先给自己安装一台空调,害,其实就是一台小电扇,接着就是我们的 360度音响体验了,其实也就是一个低音炮,来吧,最奢侈的一个设备来了,遮光板上接一个屏幕,还能连一个简单的 DVD 机器,好的吧,麻雀虽小,但是也算五脏俱全了,就像代码一样,毕竟有功能,能运行的程序就是 “好程序” 对吧哈哈哈~

(一) 小破车的辛酸

上车,打开我的小电扇,打开小音响,再放好 DVD,就可以发动小破车出发了,下车的时候熄掉火,依次关掉 DVD,音响,电扇,就可以出去了,虽然满满的仪式感,但是因为这些外接的设备都是一个一个独立的,所以不管是开启关闭,我都需要依次对其进行操作,自己忙了一天再回来在上折腾这个,别提多烦恼了

真羡慕别人的“豪华”小轿车,上车以后,一键打火,所有配套设备自动启动,凉飕飕的空调,动感的音乐

幻想着,我能不能也将我的小破车,改装成 “智能” 的模样呢?

下面我们就用代码来看一下我们的小破车设备改造

(二) 改造我的小破车

先复现一下我那些 DVD 、音响等等原来的状态

说明:这里只是为了演示,就在单线程环境下,简单的用了饿汉式单例,空调也就是上面说的小电扇,姑且这么叫好了

/**
 * 空调设备
 */
public class AirConditioner {
    // 饿汉式单例
    private static AirConditioner instance = new AirConditioner();

    public static AirConditioner getInstance() {
        return instance;
    }

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

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

这是音响

/**
 * 音响设备
 */
public class Sound {
    // 饿汉式单例
    private static Sound instance = new Sound();

    public static Sound getInstance() {
        return instance;
    }

    public void turnOn() {
        System.out.println("开启音响");
    }

    public void turnOff() {
        System.out.println("关闭音响");
    }

}

这是 DVD

public class DVDPlayer {
    // 饿汉式单例
    private static DVDPlayer instance = new DVDPlayer();

    public static DVDPlayer getInstance() {
        return instance;
    }

    public void turnOn() {
        System.out.println("开启DVD");
    }

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

}

如果使用传统的方式测试一下

public class Test {
    public static void main(String[] args) {
        // 拿到三种设备的实例
        AirConditioner airConditioner = AirConditioner.getInstance();
        DVDPlayer dvdPlayer = DVDPlayer.getInstance();
        Sound sound = Sound.getInstance();

        System.out.println("=====开启的过程=====");
        airConditioner.turnOn();
        dvdPlayer.turnOn();
        sound.turnOn();
        
        System.out.println("=====关闭的过程=====");
        airConditioner.turnOff();
        dvdPlayer.turnOff();
        sound.turnOff();
    }
}

测试结果

=====开启的过程=====
开启空调
开启DVD
开启音响
=====关闭的过程=====
关闭空调
关闭DVD
关闭音响

效果没问题了,但是可以看出来,只有短短三台设备的开关就需要执行 6 个方法,如果设备更多一些,如果操作不仅只有开关,还有一些别的,岂不是要累死,虽然咱的车是破旧了一些,但这也太折腾了

来吧,改造!

我们创建一个 CarFade 外观类,将这些细节内容都封装进去

public class CarFacade {
    private AirConditioner airConditioner;
    private DVDPlayer dvdPlayer;
    private Sound sound;

    // 在无参构造中拿到实例
    public CarFacade() {
        this.airConditioner = AirConditioner.getInstance();
        this.dvdPlayer = DVDPlayer.getInstance();
        this.sound = Sound.getInstance();
    }
    
    // 一键开启
    public void turnOn() {
        airConditioner.turnOn();
        dvdPlayer.turnOn();
        sound.turnOn();
    }
    
    // 一键关闭
    public void turnOff() {
        airConditioner.turnOff();
        dvdPlayer.turnOff();
        sound.turnOff();
    }
}

再看看如何测试呢

package cn.ideal.facade;

/**
 * @ClassName: Test
 * @Author: BWH_Steven
 * @Date: 2020/11/27 11:35
 * @Version: 1.0
 */
public class Test {
    public static void main(String[] args) {
          // 拿到三种设备的实例
        CarFacade carFacade = new CarFacade();

        System.out.println("=====开启的过程=====");
        carFacade.turnOn();

        System.out.println("=====关闭的过程=====");
        carFacade.turnOff();
    }
}

测试结果:

=====开启的过程=====
开启空调
开启DVD
开启音响
=====关闭的过程=====
关闭空调
关闭DVD
关闭音响

效果一样没问题,但是我们作为调用者,这可舒服了,我们也可以一键开关这些娱乐辅助设备了,其实这就是利用了一种简单实用的设计模式——外观模式,下面来一起看看它的概念

二 外观模式理论

(一) 概念

外观模式(门面模式):它是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式

就着上面的例子也很好理解,空调、印象、DVD,就是一个一个复杂的子系统,而我们为这几者,提供一个一致的 CarFacade ,我们就避免去访问一个一个子系统的具体细节,而只需要执行,这个 CarFacade 提供给我们对外的一个方法,其实就是达到了一个封装,精简的效果

还有例子,例如在生活中要去办户口或者注册公司等等,我们往往需要往返于多个部门之间,到处开证明,办手续,但是如果有一个综合性质的部门,统一办理对应的业务,对于用户来说就无须来回奔走,只需要根据这个综合部分对外的窗口,提交指定的材料,等待其帮你办理即可

再回到代码上,其实我们在平时的开发中已经有意或者无意的使用到了外观模式,例如高层的模块中,我们想要调用多个相对复杂的子系统,我们为了精简接口的数量,一般都会再创建一个新的类,对其进行调用封装,然后使得最终调用者,可以更加简洁容易的调用这些子系统的功能

(二) 结构

依旧分析一下其角色:

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口或者说一致的界面,使得这些子系统更加好使用
  • 子系统(Sub System)角色:实现系统的部分功能,它们其实才是我们真正想要访问的内容,客户可以通过外观角色访问它
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能

(三) 优缺点

(1) 优点

  • 简化了调用过程:只需要访问外观模式给出的对外接口即可完成调用
  • 封装性更好:使用外观模式,子系统功能及具体细节被隐藏了起来,封装性更好
  • 耦合性降低:调用者只与外观对象进行交互,不直接与子系统进行接触,降低了对子系统的依赖程序,降低了耦合
  • 符合迪米特法则

(2) 缺点

  • 不能很好的规避扩展风险:系统内部扩展子系统的时候,容易产生风险
  • 违背开闭原则:扩展子系统的时候,可能需要修改外观类,会违背开闭原则

(四) 什么时候使用外观模式

(1) 层次复杂

我们在开发初期,会有意识的使用一些常见一些架构方式,例如 MVC 等等,在层级和业务很复杂的情况下,就需要考虑在每个层级都创建一个外观对象作为入口,这样就可以为复杂的子系统提供一些简单的接口

(2) 子系统多且复杂

我们不断的更新,扩展一些功能,就会导致有很多细小却又缺不得的类,或者有一些非常复杂的系统,例如包含很多个类,这个时候我们可以考虑创建一个外观 Facade 类,简化接口,降低它们之间的依赖

(3) 调用老旧系统功能

有一些老旧的系统,几乎已经没有维护扩展的价值对了,但是其中又有一些牵扯很大的核心功能,我们在新系统中又没有足够的精力和成本去重构,只能设计一个外观 Facade 类,来交互新旧系统的代码


二境志
191 声望26 粉丝