引子:超級設計模式大賽#

開發部經理要小菜明天做關於設計模式的演講,小菜趴在桌上想了想就睡著了。他做了一個夢——「OOTV 杯超級設計模式大賽」,把 23 個 GoF 設計模式擬人化為「模式小姐」進行選秀比賽。

本章透過虛擬比賽的形式,將設計模式分為創建型、結構型、行為型三大類進行總結與對比。

物件導向的演進#

軟體開發思想經歷了幾個階段:

  • 機器語言 → 高級語言(面條式程式碼)
  • 結構化編程(面向過程):依業務流程切分功能模組
  • 物件導向:把需求理解為一個個物件,物件互相協作完成業務

面向過程關注業務流程,但流程是需求中最容易變化的部分;物件比流程更穩定、更封閉,因此現代軟體需要面向對象的設計與開發。

設計模式有助於提高思考層次。

透過複用已公認的設計,避免重複前人犯過的錯誤,並從學習他人經驗中獲益——讓我們站在山頂而不是山腳俯視設計。

模式的三大分類#

mindmap
  root((23 種設計模式))
    創建型
      抽象工廠 Abstract Factory
      建造者 Builder
      工廠方法 Factory Method
      原型 Prototype
      單例 Singleton
    結構型
      適配器 Adapter
      橋接 Bridge
      組合 Composite
      裝飾 Decorator
      外觀 Facade
      享元 Flyweight
      代理 Proxy
    行為型
      觀察者 Observer
      模板方法 Template Method
      命令 Command
      狀態 State
      職責鏈 Chain of Responsibility
      解釋器 Interpreter
      中介者 Mediator
      訪問者 Visitor
      策略 Strategy
      備忘錄 Memento
      迭代器 Iterator

創建型模式(Creational Patterns)#

抽象實例化過程。讓系統獨立於物件如何被創建、組合與表示。

  • 抽象工廠(Abstract Factory):提供一個創建一系列相關或相互依賴物件的介面,無需指定具體類
  • 建造者(Builder):將複雜物件的構建與表示分離,使同樣的構建過程可以創建不同的表示
  • 工廠方法(Factory Method):定義創建物件的介面,讓子類決定實例化哪一個類
  • 原型(Prototype):用原型實例指定創建物件的種類,並透過拷貝這些原型創建新物件
  • 單例(Singleton):保證一個類僅有一個實例,並提供一個訪問它的全域訪問點

通常設計從工廠方法開始,當需要更大彈性時,再演化到其他創建型模式。

了解多個創建型模式可以給設計者更多選擇餘地。

結構型模式(Structural Patterns)#

關注類別與物件如何組合形成更大的結構。

  • 適配器(Adapter):將一個類的介面轉換成客戶希望的另一個介面
  • 橋接(Bridge):將抽象部分與實現部分分離,使它們都可以獨立變化
  • 組合(Composite):將物件組合成樹形結構以表示「部分-整體」的層次結構
  • 裝飾(Decorator):動態地給物件添加額外職責,比生成子類更靈活
  • 外觀(Facade):為子系統的一組介面提供一個一致的界面
  • 享元(Flyweight):運用共享技術有效支援大量細粒度的物件
  • 代理(Proxy):為其他物件提供一種代理以控制對這個物件的訪問

行為型模式(Behavioral Patterns)#

關注物件之間的職責分配與通訊。

  • 觀察者(Observer):定義一對多依賴;主題狀態改變時通知所有觀察者
  • 模板方法(Template Method):定義演算法骨架,將一些步驟延遲到子類
  • 命令(Command):將請求封裝為物件;支援排隊、日誌、撤銷
  • 狀態(State):允許物件在內部狀態改變時改變行為
  • 職責鏈(Chain of Responsibility):讓多個物件都有機會處理請求,避免發送者與接收者耦合
  • 解釋器(Interpreter):給定語言,定義文法表示,並定義解釋器來解釋句子
  • 中介者(Mediator):用中介物件封裝一系列物件交互
  • 訪問者(Visitor):在不改變元素類別的前提下定義作用於這些元素的新操作
  • 策略(Strategy):定義一系列演算法,使它們可互相替換
  • 備忘錄(Memento):捕獲物件內部狀態,可恢復到先前狀態
  • 迭代器(Iterator):順序訪問聚合物件中各個元素,不暴露其內部表示

模式之間的相似與差異#

適配器 vs. 橋接 vs. 外觀#

  • 適配器:使已存在的兩個不同介面協同工作(事後補救)
  • 橋接:在設計之初就將抽象與實現解耦,讓兩者獨立演化
  • 外觀:為現存系統提供更方便的訪問介面

外觀與適配器的差別:

  • 外觀定義新的介面;適配器復用原有介面
  • 適配器使兩個已有介面協同工作;外觀為現存系統提供更方便的存取
  • 適配器針對物件;外觀針對整個子系統

代理 vs. 外觀#

  • 代理代表一個單一物件;客戶物件無法直接訪問目標物件
  • 外觀代表一個子系統;客戶物件可以直接訪問子系統中各物件

命令 vs. 職責鏈#

  • 命令:將請求封裝為物件,重點是「呼叫操作的物件」與「執行操作的物件」分離
  • 職責鏈:請求沿鏈傳遞,重點是「客戶在不明確指定接收者的情況下提交請求」

應用題:薪資管理系統範例#

夢中的決賽應用題:一家創業公司要做薪資管理系統。員工薪資分類:

  • 普通員工:月薪 + 獎金
  • 市場銷售:底薪 + 提成
  • 中高級管理人員:年薪 + 分紅
  • 兼職臨時工:時薪

系統要有選單、工具列、狀態列;初步用 SQL Server,未來可能換 Oracle、MySQL;統計圖可能自行開發或購買第三方元件。

各模式的角色:

  • 三層架構 + 業務外觀層(Facade):表示層任何變化(C/S 或 B/S)不影響業務邏輯與資料訪問
  • 觀察者:所有事件機制本質都是觀察者模式的應用,按鈕點擊 → 狀態列更新
  • 適配器:第三方統計圖元件介面不統一時,透過適配器處理
  • 策略:把多種薪資發放規則一個個封裝,可互相替換
  • 抽象工廠 + 反射:解決多資料庫切換(SQL Server ↔ Oracle ↔ MySQL)

物件導向設計原則#

設計模式體現的就是抽象的思想

  • 類別 = 對物件的抽象
  • 抽象類 = 對類別的抽象
  • 介面 = 對行為的抽象

書中反覆出現的設計原則:

  • 單一職責原則(SRP):類應僅有一個引起它變化的原因
  • 開放-封閉原則(OCP):對擴展開放,對修改封閉
  • 依賴倒轉原則(DIP):高層與低層都依賴抽象;針對介面編程
  • 里氏代換原則(LSP):子類型必須能夠替換父類型
  • 迪米特法則(LoD):不必直接通訊的類,不應該發生直接相互作用
  • 合成/聚合複用原則(CARP):盡量使用合成/聚合,盡量不要使用類繼承

架構模式番外篇:MVC#

場下八卦提到 MVC 模式(Model/View/Controller)為何沒來參賽?

  • Model:應用物件
  • View:螢幕上的表示
  • Controller:定義使用者界面對使用者輸入的響應方式

不使用 MVC 時,使用者介面設計往往將這些物件混在一起;MVC 將它們分離以提高彈性與複用性。

MVC 是多種模式的綜合應用,屬於架構模式而非單一設計模式,因此不在 23 種 GoF 設計模式之列。

沒有結束的結尾#

生活還在繼續,編程也不會結束。

書籍一定會有最後一頁,但物件導向編程之路或許才剛剛開始

夢醒了,小菜把這個故事講給同事聽,成了公司的技術明星,從菜鳥程式員蛻變為優秀軟體工程師,朝著軟體架構師的理想前進。