【設計模式】第十篇:外觀模式,開著小破車的快樂

  • 2020 年 12 月 20 日
  • 筆記

一 開著小破車的快樂

不知道大家有沒有這樣開或者坐過這樣一輛「小破車」,他能跑,但是內部娛樂或者說一些輔助的設備幾乎可以忽略不計,條件雖然艱苦了一些,但是我們還是要自己給自己創造快樂 ,夏天太熱了,先給自己安裝一台空調,害,其實就是一台小電扇,接著就是我們的 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 類,來交互新舊系統的程式碼