iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 19
0

今天的設計模式,讓我們來了解屬於結構型模式的 Facade,中文翻為門面模式表象模式外觀模式。Facade 這個詞源自法文 Façade,意思是建築物的正面。
顧名思義,門面模式是為了讓我們有一個簡潔俐落的門面(介面)而存在,把建築物裡面複雜的構成跟各種細碎的細節隱藏起來,留下對外互動的一致窗口。它的目的是用一個高層介面包裝各個子系統,由這個統一對外介面與客戶端進行溝通。例如為一個 library、frameworrk 或任何其他複雜的物件組成提供一個簡化的介面。

Facade 使用時機

  1. 系統太過複雜的時候,作為整合之用,提供讓客戶端快速達成目的捷徑。
  2. 以門面模式將子系統組織成一層一層的架構,用門面去定義子系統每一層的進入點。這麼一來可以透過只用門面溝通來降低子系統之間的耦合。

舉例來說

門面模式就像客服專員,是你可以接觸到這家公司所有服務的窗口。透過一個電話的語音介面,你就可以知道商品存貨、物流配送、處理退換貨等等。你不必親自去接洽每個不同功能的部門,也不用了解他們內部每天運作的細節,只要透過客服專員幫你快速解決眼前的問題就好。

圖片引用自 Refactoring Guru

Facade 程式碼範例

請大家先一起回到 Apple TV 出現讓一切變得太簡單前(著實不久之前)的時代。今天我們打造了一個夢想中的超級家庭劇院(甚至有爆米花機)。為了和朋友們一起觀看期待已久、特地租來的電影 DVD,我們還要跨越最後一道關卡,就是所有影音裝置的軟硬體設置!以下是我們需要執行的任務:

  1. 啟動爆米花機
  2. 開始製作爆米花
  3. 房間燈光調暗
  4. 拉下投影幕
  5. 啟動投影機
  6. 切換投影機 Input 到 DVD 播放器
  7. 開啟音響
  8. 將音響連接到 DVD 播放器
  9. 將音響模式調整為環繞音效
  10. 將音響音量調整至中間值 (5)
  11. 啟動 DVD 播放器
  12. 開始影片放映

使用 Swift,寫成程式碼便會如下所示,我們發現只是想單純的看個電影,就必須用到六個不同的類別。

popper.on()
popper.pop()

lights.dim()

screen.down()

projector.on()
projector.setInput(to: dvdPlayer)

amplifier.on()
amplifier.setTo(dvdPlayer)
amplifier.setSurroundSound()
amplifier.setVolume(5)

dvdPlayer.on()
dvdPlyer.play(movie)

不只是這樣,當我們看完電影,該怎麼把所有設備關掉?要重新依序做一遍相反的程序嗎?如果今天我們要用同一套設備聽 CD,會是用一樣複雜的方式做設定嗎?如果有一天你決定要升級某些硬體,該怎麼調整這個流程?
現在我們可以明顯感受到這個家庭劇院的系統複雜性了,就讓門面模式來解決這個問題,好讓我們能夠好好看場電影,不為這些開開關關、連不連線的瑣事心煩!

Facade Pattern Saves the Day!

門面模式讓我們有了一個厲害的遙控器,讓我們只需要按一個按鈕,就可以透過遙控器上的捷徑快速達成目的。首先我們來建構這個寫作門面,讀作遙控器的類別,讓它取得房間內所有硬體裝置的控制權限。

class HomeTheaterFacade {
    let amplifier: Amplifier
    let dvdPlayer: DvdPlayer
    let cdPlayer: CdPlayer
    let projector: Projector
    let lights: TheaterLights
    let screen: Screen
    let popper: PopcornPopper
    
    init(amplifier: Amplifier,
         dvdPlayer: DvdPlayer,
         cdPlayer: CdPlayer,
         projector: Projector,
         lights: TheaterLights,
         screen: Screen,
         popper: PopcornPopper) {
        
        self.amplifier = amplifier
        self.dvdPlayer = dvdPlayer
        self.cdPlayer = cdPlayer
        self.projector = projector
        self.lights = lights
        self.screen = screen
        self.popper = popper
    }
    
    // Facade 要實作的方法
}

接著就來實作這個簡化的介面,將子系統的元件組合成統一的介面。以下我們實作 HomeTheaterFacadewatchMovie()endMovie() 兩個方法:

func watchMovie(movie: String) {
    print("--準備看電影--")
    popper.on()
    popper.pop()
    lights.dim()
    screen.down()
    projector.on()
    amplifier.on()
    amplifier.setTo(dvdPlayer)
    amplifier.setSurroundSound()
    amplifier.setVolume(5)
    dvdPlayer.on()
    dvdPlayer.play(movie)
}

func endMovie() {
    print("--關閉家庭劇院--")
    popper.off()
    lights.on()
    screen.up()
    projector.off()
    amplifier.off()
    dvdPlayer.stop()
    dvdPlayer.eject()
    dvdPlayer.off()
}

現在有了遙控器,用超級家庭劇院看電影變得非常簡單了!只需要呼叫 homeTheaterFacade.watchMovie() 就能開心看電影了,看完電影後關閉各個設備也能一鍵解決;同理,要用家庭劇院聽 CD 當然也不是問題。

homeTheaterFacade.watchMovie("名偵探柯南:貝克街的亡靈")
homeTheaterFacade.endMovie()
homeTheaterFacade.playCd("歡唱童謠")

Facade 優缺點

優點是能讓你的程式與複雜的子系統隔離開來。缺點是 Facade 這個物件可能會變成跟客戶端所有類別都耦合的 God Object(Anti-Pattern,一個了解過多或者負責過多的物件)。

以上是今天的門面模式,謝謝你的閱覽,我們明天再見!

參考資料

  1. Refactoring Guru - Facade Pattern
  2. Head First Design Patterns: A Brain-friendly Guide

作者:Jo


上一篇
[Design Pattern] Chain of Responsibility 責任鍊模式
下一篇
[Architectural Pattern] MVP pattern for Android
系列文
什麼?又是/不只是 Design Patterns!?32

尚未有邦友留言

立即登入留言