相关文章推荐
彷徨的机器人  ·  [Design Pattern] ...·  1 年前    · 

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

Facade 使用時機

  • 系統太過複雜的時候,作為整合之用,提供讓客戶端快速達成目的捷徑。
  • 以門面模式將子系統組織成一層一層的架構,用門面去定義子系統每一層的進入點。這麼一來可以透過只用門面溝通來降低子系統之間的耦合。
  • 門面模式就像客服專員,是你可以接觸到這家公司所有服務的窗口。透過一個電話的語音介面,你就可以知道商品存貨、物流配送、處理退換貨等等。你不必親自去接洽每個不同功能的部門,也不用了解他們內部每天運作的細節,只要透過客服專員幫你快速解決眼前的問題就好。
    圖片引用自 Refactoring Guru

    Facade 程式碼範例

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

  • 啟動爆米花機
  • 開始製作爆米花
  • 房間燈光調暗
  • 拉下投影幕
  • 啟動投影機
  • 切換投影機 Input 到 DVD 播放器
  • 將音響連接到 DVD 播放器
  • 將音響模式調整為環繞音效
  • 將音響音量調整至中間值 (5)
  • 啟動 DVD 播放器
  • 開始影片放映
  • 使用 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,一個了解過多或者負責過多的物件)。

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

    Refactoring Guru - Facade Pattern
  • Head First Design Patterns: A Brain-friendly Guide
  • 作者:Jo