本章主軸#
本章透過將物件導向典範與「結構化程式設計(structured programming)」做對照,引導讀者理解物件導向(object-oriented)誕生的原因。物件導向是為了應對結構化方法所無法妥善解決的問題(特別是需求變動)才出現的;唯有先看清這些挑戰,才能體會物件導向的真正優勢。
本章不會把你變成 OO 專家,也不會涵蓋所有基礎概念,但它會建立一個正確的心智模型,讓你準備好接續學習設計模式。
結構化方法的盲點:功能分解#
什麼是功能分解(functional decomposition)#
當你被要求寫一段程式從資料庫讀出形狀並顯示時,最直覺的做法是把問題拆成一連串步驟:
- 找到資料庫中的形狀清單
- 開啟形狀清單
- 依規則排序
- 在螢幕上顯示每個形狀
每個步驟還可以再往下分。這就是「功能分解」——分析師把問題拆成一個個函式來解決。
為什麼這種做法會出問題#
「主程式」承擔過重責任:負責協調、呼叫順序、確保所有功能正確運作
無法優雅地應對變動:當需求改變時,所有使用相關步驟的函式幾乎都得跟著改
修改即帶來風險:
許多 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(抽象類別) | 定義 display、getX、getY |
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),導致需求一變就大規模波及
- 物件導向透過「責任轉移」「隔離變動」「抽象與多型」應對需求變化
- 物件不只是「資料 + 方法」,而是「對自身負責的責任主體」
- 封裝不僅隱藏資料,更是隱藏「可能變動的一切」
- 學習物件導向必須同時從概念、規格、實作三個層次思考