Practical Object-Oriented Design Book 心得&整理 (1)

Z-xuan Hong
8 min readApr 2, 2021

--

參考書籍

最近在閒暇之餘,重新看完了Practical Object-Oriented Design這本書,在為數眾多的物件導向書籍中,這本書在我心目中的排名算是數一數二的。

為什麼?因為不管是學習DDD、TDD、Design Pattern、Refactoring,這些書中都假設我們對於物件導向分析與設計有一定的基本功,DDD專注建立能表達領域問題的Domain Model、TDD透過測試先行的方式從中獲得Feedback來改善軟體設計、Design Pattern則是提供優良的物件導向解法、Refactoring教導我們透過系統性的方式以微小步驟逐步改善軟體設計。

要能夠做到上面講的那些事情,沒有物件導向基本功是幾乎不可能做到的

  • DDD:為了建立Domain Model,我們需要有類別與物件,但…這些類別與物件是如何生出來的?
  • TDD:為了測試先行,我們需要設計物件的Public API (也就是此物件能夠接收哪些message),但…如何設計比較好?
  • Design Pattern:提供優良的物件導向解法給你,教導你如何設計物件與物件之間的互動,但為什麼根據書中的方式設計會比其他方式還要好,為什麼Composition遠遠比Inheritance還要好?一切都很不直覺?
  • Refactoring:當你想要重構時,如何思考重構最終的目標?在高耦合系統重構時,時常一堆模組同時壞掉,這樣要如何以逐步的方式慢慢重構?

上述書籍畢竟都是大師們淬鍊出來的結晶,如果我們連原料都沒有的話要如何淬鍊出跟大師們一樣的結晶呢?因此這篇文章將會介紹這本書,讓大家對於原料 (物件)的使用能夠有基本的認知。這樣一來不管是DDD、TDD、Design Pattern、Refactoring都能更容易上手。

未來文章根據下方依序介紹:

  • What is Object-Oriented Design (*)
  • Designing Classes with a Single Responsibility
  • Managing Dependencies
  • Creating Flexible Interfaces
  • Reducing Costs with Duck Typing
  • Acquiring Behavior through Inheritance
  • Sharing Role Behavior with Module
  • Combining Object with Composition
  • Designing Cost-Effective Tests

What is Object-Oriented Design

  • Introduction

起床、刷牙、上廁所、上學、下課、睡覺,以條列式步驟方式來解決問題,這是程序式導向的概念,總統負責處理國家重要決策、市長處理縣市重要決策、老闆負責處理公司重要決策、員工負責處理被分配到的任務,每個角色都負責自己的行為,這是物件導向的概念。

上述兩種解決問題的方式與技術無關,而是與思考角度有關,程序式導向以條列式的方式思考、物件導向以行為的方式思考,因此難維護的物件導向系統不在於技術上的問題,而在於思考角度的錯誤,因此為了要掌握物件,我們需要以物件的思維進行思考。

物件導向中:物件擁有接收一系列的message (也就是Public API),使用者只需傳送message給物件即可讓物件達成使用者的目標,不需要了解此物件實際上做了哪些事情

我們去餐廳吃飯時,會向服務生點餐,過了一段時間後,服務生便會端上餐點給我們,接著享用。

以物件導向的概念來解釋:點餐就是我們傳送給服務生物件的message,目標則是等餐點享用,在我們跟服務生點餐後,他可能會先去廚房通知廚師要做哪些餐點、接著廚師準備食材、準備擺盤、通知服務生餐點好了,如果客人知道這些事情,當食材替換、廚師換人、盤子壞掉時皆會受到影響,就喪失了去餐廳吃飯的意義了。

客人只與餐點、服務生接觸,至於廚房後面所有事情一蓋不知。

但為什麼要使用物件導向、為什麼要設計、為什麼要測試?答案很簡單:為了降低開發成本。

如果說開發好的軟體上線之後就不需要修改,那麼使用物件導向、設計分析、撰寫測試就沒那麼吸引人了,但現實是骨感的,客戶請你開發軟體,但不知道他想要什麼,客戶只會在軟體從他腦中具現化後才知道他要什麼,所以才會有人說軟體開發不變的定律就是改變,也因如此物件導向、設計、測試就變得有意義了。

當系統不斷修改、加新功能時,舊有行為與現在行為已經大不相同,既有的架構逐漸無法負擔新的功能,如果沒有加以調整的話,架構則會逐漸腐化,讓每一次增加功能的成本大幅上升,最後導致專案失敗收場。

  • Why Change is Hard?

物件導向系統是由一堆物件組合而成,物件與物件之間透過message的方式互動來達到期望行為,物件需要與物件溝通,這樣就產生了依賴 (Depedencies),Depedencies也就是讓改變成本大幅上升的主要原因。

當元件A依賴於元件B時,如果元件B改變,元件A也會被迫改變。繼承是物件導向中最強烈的依賴,因為子類別依賴於父類別全部的功能,當父類別變更時,底下子類別很容易受到影響。之後也會介紹如何正確使用繼承。

作者在對於物件導向設計的定義如下:

Object-oriented design is about managing dependencies. It is a set of coding techniques that arrange dependencies such that objects can tolerate change.

如何控制好依賴關係,並且使用正確的方式排列組合都是非常重要的課題,而Design Pattern、Dependency Injection都是透過不同排列組合來降低改變的成本。

範例1:

如下面程式碼所示:Order物件與Database物件溝通,因此Order物件依賴於Database物件,所以當Database物件修改時,Order物件也有可能受到影響。

public class Order {
private Long id;

public void calculatePrice() {
List<Item> items = Database.getItemByOrderId(id);
// calculation code
}
}

在測試時為了產生Order物件我們也需要產生Database物件,而測試對於Database的依賴也是導致測試不穩定、執行速度緩慢等原因。

商業邏輯重於計算複雜的規則、Database用來儲存資料,兩種概念不太有交集,因此我們不希望彼此之間互相影響,Depedencies帶來修改的成本就是Entity不應該耦合到資料庫的根本原因,這也是為什麼DDD、Clean Architecture一直提倡Domain Model Isolation,就是為了讓商業邏輯可以獨立開發、測試、演化,不受到任何外部的干擾 (商業邏輯與外部模組獨立修改)。

範例2:

如下圖所示:當設計不良時,代表我們沒有控制好依賴關係,物件A與物件B互動、物件B與物件C互動、物件B與物件A互動、物件C與物件A互動,這些互動都會形成所謂的Depedencies。不良的物件會讓其客戶端知道過多Context,而當這些Context變化時,客戶端便會連帶受到影響。

依賴控管不良的系統

修改小元件、間接影響大元件、間接影響子系統、間接影響整個系統,這樣的影響也稱之為Cascade Change,也是造成改變成本如此之高的原因,牽一髮而動全身也是這種系統的最佳比喻。

範例3:

如下圖所示:如果是良好的設計,我們會好好控管物件之間的依賴關係,物件不會讓其客戶端知道過多的Context,修改所影響的成本自然也沒那麼大了。

依賴控管良好的系統

有了物件導向設計的基本認知之後,下一章將會介紹如何設計符合單一責任的類別。

結論:

  • 物件導向開發與技術無關,而與思維角度有關。在於達到客戶期望行為,而不是條列式步驟。
  • 物件擁有一系列的message,應該要根據客戶端所期望的行為定義,來降低客戶端對於物件的耦合。
  • 物件導向設計在於依賴控管,不良的依賴會導致系統修改的成本變大。
  • 物件不應該讓其客戶端擁有過多Context,避免Context的變化影響客戶端。

--

--

Z-xuan Hong
Z-xuan Hong

Written by Z-xuan Hong

熱愛軟體開發、物件導向、自動化測試、框架設計,擅長透過軟工幫助企業達到成本最小化、價值最大化。單元測試企業內訓、導入請私訊信箱:t107598042@ntut.org.tw。