架構與大局思維#
Think About the Big Picture——系統內的決策必須與大局一致。
「大局」涵蓋兩層:
- 系統層級:整體架構、商業目的、未來方向。
- 環境層級:所屬部門或公司既有的框架、共用元件、整合慣例。
在重大設計決策前,問一句:「這個選擇能撐到下一輪需求?還是只解今天這個問題?」前者能避免再走一次架構手術。
兩個整體取捨:中央伺服器 vs. 對等架構#
當 Sam 要開第二家店,作者面對典型的架構取捨:
- 中央伺服器:所有店共用
CDDiscCollection與RentalCollection,便於跨店報表與統一管理;但網路斷線時無法營運。 - 對等架構(peer-to-peer):每店保有自己的集合,僅透過介面查詢另一家店。
interface StoreServiceProvider
Boolean is_physical_id_in_cddisc_collection(PhysicalID physical_id)
Boolean show_availability_of_CD_release(UPCCode upc_code)最終選擇:
- Sam 沒有跨店報表需求 → 中央伺服器的好處不顯著。
- 對等架構建置成本低、抗網路風險高。
- 若未來需求改變,仍可重新評估。

Figure 10-1. CDCatalogItemInStoreCollection sequence diagram
架構選擇不是「哪個比較好」,而是「在當前需求與資源下,哪個取捨最划算」。把選擇與理由寫進設計日誌,未來就有重新評估的依據。
把缺漏的概念提名為類別#
第二家店出現時,作者發現第一版漏掉了 Store 這個抽象:
class Store
CommonString name
Address address
PhoneNumber phone_number
class StoreCollection
Store where_is_physical_id_in_cddisc_collection(PhysicalID)
Store[] show_availability_of_CD_release(UPCCode)
Store[] find_all_stores()這呼應 Clump Data So That There Is Less to Think About——把屬於同一概念的多個屬性群聚起來。
對外介面:別讓冷風吹進來#
當系統開始對外提供服務(Web Service、API),就需要一條額外的防線。
Don’t Let the Cold Air In——對外的介面必須做輸入驗證與紀錄。
實踐:
- 所有外部輸入都應從原始字串轉成
SymbolFreeString或對應 ADT,移除危險字元(;、?等)。 - 用「tainted variable」概念:未驗證的輸入被視為汙染,驗證過後才標記為乾淨。
- 對外端點要記錄 access log,便於後續分析合法用戶行為與惡意攻擊。
範例:對外服務 ExternalServicesProvider 與內部 StoreServiceProvider 分屬兩個介面,避免內部行為被直接暴露:
interface ExternalServicesProvider
StoreExternalDTO[] show_availability_of_CD_release(UPCCode upc_code)
class StoreExternalDTO
CommonString name
Address address
PhoneNumber phone_number內部與外部使用不同 DTO,內部變動才不會無預警地影響合作夥伴的整合。
用既有輪子(Don’t Reinvent the Wheel)#
Sam 第三家店要開到 Canada 或 Mexico 時,要處理多幣別。作者把 Dollar 改名為 Money:
enumeration CurrencyID {USD, CAD, MXN}
class Money
CurrencyID id
Money(CurrencyID id)
// 同 Dollar 的操作與屬性再把 ISO 4217 引入並重用 Java 內建 Currency、CurrencyFormat:
Don’t Reinvent the Wheel——找既存解再說,標準與現成元件能省下無數細節。
Don’t Overclassify——貨幣只在資料上不同,行為相同 → 一個類別 + enum 即可,不必為每個貨幣寫類別。
避免過早通用化#
Avoid Premature Generalization——先解決具體問題再通用化。
理由:
- 一開始就追求「通用支付」會被無數匯率、稅務、結算規則拖住。
- 在 Sam 的場景把單一幣別解透,累積經驗後再泛化,反而能設計出貼合實務的
Money。 - Brad Appleton 提醒:即使有通用化的點子,也不必馬上做;至少要避免在當下做決定時,把未來的彈性堵死。
真實案例:列印伺服器#
Chapter 15 的列印伺服器案例展示在現實系統落地預重構:
- 每個元件都圍繞單一抽象設計(PrintJob、ReleaseStation、PaymentMethod)。
- 透過 message-based 通訊解耦:客戶端送 request、伺服器回應 reply,每種訊息對應一個類別。
- 在時程壓力下(10 週交差貿易展示),作者沒有把所有 Extreme Abstraction、Extreme Separation 的指南都套滿,但仍把「介面契約」與「最小可動」做好。

Figure 15-1. Sequence diagram for handling a message
在時間限制下,預重構的價值在於先想清楚介面與責任分配,內部實作可以接受不完美。
真實案例:反垃圾郵件#
Chapter 16 的反垃圾郵件案例強調職責分離:
ReceivingMailServer:收信並把信件交給檢查器。ReceivedMailExaminer:負責跑垃圾郵件規則並標記。MailReport:把檢查結果以使用者偏好的形式(附在訊息末、加在 header、忽略)呈現。
反垃圾郵件本質就是 Pipeline + Strategy 的組合。每段管線只做一件事,誰要更新規則就只動
ReceivedMailExaminer的策略物件。

Figure 16-1. Process of delivering email

Figure 16-2. State transition for SMTP
從具體到抽象:把 Sam 的系統泛化#
第十四章末尾總結:可以把 Sam 系統泛化為「租借任意可識別的物件」:
CDDisc→RentableItemCDRelease→CatalogItemRentalOperations、CatalogOperations介面分離仍然適用。- 特定領域方法(如「依演出者搜尋」)對通用版不適用,可作為衍生介面。
在具體場景中累積足夠經驗後,再來做泛化會更踏實——這是「先解具體再通用」的另一個示範。
架構與設計指南總結#
| 指南 | 訊息 |
|---|---|
| Think About the Big Picture | 設計決策要與大局一致 |
| Don’t Let the Cold Air In | 對外介面驗證與紀錄 |
| Don’t Reinvent the Wheel | 用既有元件 |
| Don’t Overclassify | 用 enum 取代為每個變化建類別 |
| Avoid Premature Generalization | 先解具體再通用 |
| Clump Data So That There Is Less to Think About | 缺漏的抽象要立刻提名 |
| Decouple with Associations | 跨概念互動要靠關聯類別解耦 |