物件的三條法則#
借用 Asimov 的《I, Robot》三定律,作者提出物件三法則,可同時套用在類別與整體程式:
- 物件應做其方法所稱之事(do what its methods say it does)
- 物件不應造成傷害(do no harm)
- 物件無法執行所請求操作時應通知使用者(notify if unable to perform)
第一定律:方法名稱要等於行為#
呼應 A Rose by Any Other Name Is Not a Rose 與最少驚奇原則(Principle of Least Surprises)。
remove與delete在多數 UI 中含義不同:remove通常只是從集合移除,delete才是徹底刪除。- 自定義方法的語意要與這類常識保持一致,避免讀者誤解。
第二定律:不要佔用不必要的資源#
範例:早期 GUI widget 在建構時開設定檔,銷毀時才關閉。系統限制同時開檔 20 個 → 第 21 個 widget 創不出來。
- 物件需要學會「自己生活在自己能力範圍內」,及時釋放資源。
- 若語言不保證何時銷毀(如 Java),應提供
dispose()等方法手動釋放。 - 設計協作要相互尊重——不要把鄰居物件的資源也一起霸佔。
第三定律:不要沉默失敗#
Never Be Silent——遇到錯誤就要報錯,不要默默吞掉。
- 若
add()發現要加入的元素已存在,就該報錯而不是默默覆蓋。 - 報錯方式(例外或回傳碼)依語言與團隊指南而定,重點是「呼叫端有機會知道」。
內聚與耦合#
Honor the Class Maxims——讓類別保持高內聚、低耦合。
內聚(Cohesion)#
- 每個類別只代表一個抽象。一句話說不清楚,就是混了多個概念。
- 範例:
Time同時代表「時鐘時間」與「時間區間」時,加減秒數會出現逾界問題;先寫一句話定義就能避免。
耦合(Coupling)#
- 緊耦合:依賴對方實作,是要避免的。
- 共同耦合:透過介面互動,實作換了不必動上層。
- 鬆耦合:只仰賴對方存在。
多數情況追求 common 或 loose——「Design to an interface, not an implementation.」
方法的歸屬#
Place Methods in Classes Based on What They Need——若方法不需實例資料就不該屬於該類別;若它只需實例資料就該屬於該類別。
範例:break_into_lines(text, charsPerLine) 不操作 CDRelease 屬性,三個歸屬選擇:
- 工具類別:
StringHelper.break_into_lines(...),純函式取向。 - String 衍生類別:
MyString.break_into_lines(...),物件能自我處理但容易塞滿不相關方法。 - 保留在原類別:易於閱讀但違反原則,且難以重用。
多型:繼承還是介面#
多型(polymorphism)有兩種用途:
- 同介面、同行為、不同實作(例如印表機驅動)。
- 同介面、不同行為(例如 Proxy 類別)。
實踐方式:
- 繼承:衍生類別共享基底類別介面。
- 介面:在 Java/C# 中以多介面表達多重關係。
- Duck Typing(Ruby 等):第三條路,只要方法簽章一致就能用。
Avoid Premature Inheritance——繼承需要時間演化。
Think Interfaces, Not Inheritance——介面提供更靈活的關係。
抉擇:
- 「需要
switch判斷類別行為」是繼承時機的訊號;多處出現相同switch更該用繼承。 Square與EquilateralTriangle都是 RegularPolygon——用介面比硬塞繼承更恰當。- 委派比繼承更強:把行為委派給策略物件(Strategy 模式),就能避免類別爆炸。

Figure 6-1. Shapes using inheritance

Figure 6-2. Shapes using an interface

Figure 6-3. Shapes using an additional interface

Figure 6-4. A framework using strategy
不要過度分類(Don’t Overclassify)#
Don’t Overclassify——根據行為而非資料來分離概念。
範例:CDRelease 有 NewRelease、GoldenOldie、Regular 三類。
- 不同類別:建立
NewReleaseCD、GoldenOldieCD、RegularCD三類,繼承CDRelease。 - 同類別不同物件:在
CDRelease中存一個CDCategory屬性,行為差異透過查表處理。
判斷準則:
- 三類差別只是值(base rental period 為 2、3、4 天)→ 一個類別 + 列舉就夠。
- 若行為差異很多(不只是值),才考慮繼承。

Figure 5-1. A CDRelease inheritance hierarchy

Figure 5-2. CDRelease using an enumerated category
行為若真的不同,才回到繼承;其後又能進一步用委派取代繼承:

Figure 5-3. CDRelease with inheritance

Figure 5-4. CDRelease with delegation
政策與實作分離#
Separate Policy from Implementation——把 what 與 how 拆開,what 才會易讀易維護。
寫程式建議流程:
- 先寫意圖(policy):
if (a_customer.is_good_customer()) a_customer.provide_discount(); - 再實作
is_good_customer()與provide_discount()的判斷與邏輯。
一件事做好就好#
Do a Little Job Well and You May Be Called Upon Often——專注做小事的方法/類別更易被重用。
借鑒 Unix 哲學:
wc計算行數、字數、字元數;ls列檔案;ls | wc算出檔案數。File只做基本讀寫;FileWithLines用於按行讀;FileWithKeywords用於 keyword 取值。各自負責一件事,靠委派組裝。
顯式命名與避免重載#
Overloading Functions Can Become Overloading——使用獨特名稱讓函式自我描述。
不要:
search(int an_int)
search(String a_string)應該:
search_for_first_name(CommonString a_string)
search_for_last_name(CommonString a_string)理由:
- 文字搜尋「search(」會找到所有版本,難以鎖定特定方法。
- 同一型別下的不同搜尋語意,無法共存於同名重載。
- 運算子重載也是同理:
Time t + 5看不出加的是時、分、秒,改t.add_hours(5)立刻清楚。
偏離與例外的程式內結構#
例外應分為兩個階層:
- 預期偏離:使用者可採取行動,例如
CustomerIDNotFoundException。 - 意外錯誤:應終止或上報,例如
DatabaseUnreachableException。
在 Java/C# 中,checked exceptions 強制呼叫端處理,適合預期偏離;unchecked exceptions 適合「應終止」型錯誤。
層次處理:
- 低層拋具體例外(
ChainBrokenException)。 - 中層轉成抽象例外(
UnableToDeliverException),上層只看抽象。 - 訊息中保留底層細節作為說明,呈現給使用者時再轉成可採取行動的描述。
用狀態思考物件行為#
物件常擁有狀態,行為依當下狀態而變。
See What Condition Your Condition Is In——用狀態為基礎分析物件行為。
範例:
File:NotOpen、OpenForReading、OpenForWriting、OpenForReadingAndWriting。CDDisc:Rented/NotRented、InService/Lost。Customer:Regular、Inactive、NeverAgain。
實作選擇:
- 二元狀態:用布林屬性或「是否擁有 Rental」即可。
- 多狀態:用列舉屬性(
enumeration CustomerState)。 - 狀態相依的行為較多:採用 State 模式,把行為委派給狀態物件,可在執行期切換狀態而不必改類別。

Figure 9-2. CDDisc state diagram

Figure 9-3. CDDisc service state diagram

Figure 9-4. Customer with states
不要把新狀態硬塞入現有狀態。電子機票案例中,「ticketed」概念若只剩下「是否被列印」就無法描述新流程,最終讓使用者鑽漏洞。
完整覆蓋所有狀態#
當設計新狀態時,要主動檢查:
Rental的Current/Completed與Overdue/Late是兩個正交軸:Current×Overdue/NotOverdue:動態,會因時間經過而變。Completed×Late/NotLate:靜態,租借結束後不再改變。Completed不會Overdue、Current不會Late。
用狀態機分析時,AT&T 早期交換機案例的設計者花了數月窮舉所有狀態與轉換,最後讓編碼者幾週內完成;這種前期投資撐起多年的可維護性。
程式內結構指南總結#
| 指南 | 訊息 |
|---|---|
| Three Laws of Objects | 做名實相符、無害、有錯就報 |
| Honor the Class Maxims | 高內聚、低耦合 |
| Place Methods in Classes Based on What They Need | 看資料需求決定方法歸屬 |
| Avoid Premature Inheritance | 繼承需要時間演化 |
| Think Interfaces, Not Inheritance | 介面比繼承更靈活 |
| Don’t Overclassify | 依行為而非資料分類 |
| Separate Policy from Implementation | what 與 how 分離 |
| Do a Little Job Well | 一件事做好,重用更容易 |
| Overloading Functions Can Become Overloading | 用獨特名避免重載 |
| Never Be Silent | 不要沉默失敗 |
| See What Condition Your Condition Is In | 用狀態為基礎分析行為 |