本章主軸#

本章透過將物件導向典範與「結構化程式設計(structured programming)」做對照,引導讀者理解物件導向(object-oriented)誕生的原因。物件導向是為了應對結構化方法所無法妥善解決的問題(特別是需求變動)才出現的;唯有先看清這些挑戰,才能體會物件導向的真正優勢。

本章不會把你變成 OO 專家,也不會涵蓋所有基礎概念,但它會建立一個正確的心智模型,讓你準備好接續學習設計模式。

結構化方法的盲點:功能分解#

什麼是功能分解(functional decomposition)#

當你被要求寫一段程式從資料庫讀出形狀並顯示時,最直覺的做法是把問題拆成一連串步驟:

  1. 找到資料庫中的形狀清單
  2. 開啟形狀清單
  3. 依規則排序
  4. 在螢幕上顯示每個形狀

每個步驟還可以再往下分。這就是「功能分解」——分析師把問題拆成一個個函式來解決。

為什麼這種做法會出問題#

  • 「主程式」承擔過重責任:負責協調、呼叫順序、確保所有功能正確運作

  • 無法優雅地應對變動:當需求改變時,所有使用相關步驟的函式幾乎都得跟著改

  • 修改即帶來風險

    許多 bug 都是改程式時引入的。

需求一定會變#

沒有任何人會說「我們的需求清楚完整,足以涵蓋未來五年所有功能」。需求永遠是不完整、模糊、會變動的。

需求改變的原因很單純:

  • 使用者在與開發者討論後,看見更多可能性
  • 開發者在動手實作後,更了解問題領域
  • 整體開發環境本身也持續變動

我們無法預測會改什麼,但通常可以預測「會在哪裡改」。物件導向的真正威力之一,就是把這些「易變區域」隔離起來,避免影響其他程式碼。

凝聚與耦合:功能分解的根本問題#

  • 弱凝聚(weak cohesion):類別或函式裡塞太多不相關的工作 → 程式像「god objects」
  • 強耦合(tight coupling):函式與函式之間連動嚴重 → 改一個地方,意外波及他處

維護與除錯的時間,大部分不是花在修 bug,而是花在「弄懂程式碼如何運作」與「找出 bug 並避免改動引發的副作用」。

從生活情境看新觀念:「請去下一間教室」#

書中以教室管理舉例:

  • 結構化做法:講師為每位學生決定下一堂課地點與走法,自己幾乎要瘋掉
  • 物件導向做法:把走廊地圖貼出來,告訴學生「請自行前往下一間教室」

關鍵差異在於:

  • 責任轉移:每個物件對自己負責
  • 以介面通訊:講師可以對所有學生用同樣的指令說話
  • 隔離變動:新增「研究生」這種類型時,主控程式不需改動

Fowler 的三種視角#

理解物件導向時,要同時從以下三個層次思考(出自 Martin Fowler 的《UML Distilled》):

視角提問
概念(Conceptual)「我負責什麼?」
規格(Specification)「我要怎麼被使用?」
實作(Implementation)「我如何完成自己的職責?」

物件導向常被「只在實作層次」討論——程式碼與資料。但真正的力量來自於先在概念與規格層次思考。

物件導向核心概念#

物件(object)#

不要把物件想成「資料 + 方法」,而要想成「擁有責任的東西」:

  • 在概念層次:一組責任
  • 在規格層次:一組可被呼叫的方法
  • 在實作層次:程式碼、資料與兩者間的計算互動

類別、實例、實例化#

  • 類別(class):物件的藍圖,定義行為與資料
  • 實例(instance):依類別建立出的特定物件
  • 實例化(instantiation):建立實例的過程

抽象類別(abstract class)#

抽象類別是「一組相關類別共同的概念佔位符」。

  • 概念上:定義一群相關類別「能做什麼」
  • 規格上:作為其衍生類別的共同介面
  • 實作上:通常不被實例化,但可包含所有衍生類別共用的方法

介面(interface)可視為「沒有共同實作」的抽象類別,純粹用於定義一組類別必須實作的方法。

繼承與 is-a 關係#

  • 類別可以繼承自其他類別,形成 is-a 關係
  • 衍生類別繼承基底類別的屬性,可選擇沿用或覆寫行為

封裝(encapsulation)#

封裝不只是隱藏資料——它泛指一切「隱藏」:型別隱藏、實作隱藏、設計隱藏皆屬之。

封裝的好處:

  • 使用者不必煩惱實作細節
  • 實作可以替換,不影響呼叫者
  • 自動形成鬆耦合

多型(polymorphism)#

「Poly」表示「多」,「morph」表示「形」,合在一起就是「多種形式」。

  • 透過抽象參考呼叫方法時,實際行為由具體衍生型別決定
  • 講師對學生喊「去下一堂課」,研究生與一般生會展現不同行為

形狀程式:物件導向版本#

類別主要責任
ShapeDataBase取得指定的形狀集合
Shape(抽象類別)定義 displaygetXgetY
Square / Circle(衍生)各自實作 display
Collection儲存與排序形狀,呼叫每個形狀的 display
Display真正在螢幕上畫線、畫圓

面對新需求時:

  • 新增三角形:只需新增一個 Shape 子類別,並實作其 display
  • 更換排序演算法:只需修改 Collection 內的方法

特殊方法:建構子與解構子#

建構子(constructor)#

  • 物件被建立時自動執行,用來初始化狀態
  • 集中管理初始化流程,可大幅減少「未初始化變數」這類隱晦 bug

解構子(destructor / finalizer)#

  • 物件被銷毀時自動執行,用來釋放資源
  • C++ 與 C# 稱 destructor,Java 稱 finalizer
  • Java 因有垃圾回收(garbage collection),其重要性不如 C++

本章要記住的事#

  • 結構化方法把焦點放錯位置(focus on functions),導致需求一變就大規模波及
  • 物件導向透過「責任轉移」「隔離變動」「抽象與多型」應對需求變化
  • 物件不只是「資料 + 方法」,而是「對自身負責的責任主體」
  • 封裝不僅隱藏資料,更是隱藏「可能變動的一切」
  • 學習物件導向必須同時從概念、規格、實作三個層次思考