Kent Beck 的實作模式

Kent Beck 的實作模式

Bear


Chapter 4 動機


軟體成本 = 初始成本 + 維護成本


維護成本 = 理解成本 + 修改成本

+ 測試成本 + 部署成本


在初期的開發中投入更多精力,來減少維護的需要?


Kent Beck 的策略


注重程式設計師之間的溝通

減少理解程式碼的代價


記錄自己的實作模式,並嚴格地遵守它


模式給人們和經濟的影響


Chapter 5 類別


類別 (Class)

這些資料應該放在一起,還有這些邏輯也是。


資料會變,邏輯不變

學會如何用類別來包裝邏輯和如何表達邏輯的變化。


繼承

把多個類別放進繼承體系可以縮減程式碼的數量。


簡單的超類別名稱 (Simple Superclass Name)

位於繼承體系上的根類別,應該有個簡單的名字,用以描繪它的隱喻。


命名

在所有的命名中,類別的命名是最重要的。


簡短與表達力之間存在張力

類別名稱應該簡單扼要,但有時候需要好幾個單字才能足夠精確。


隱喻

替計算邏輯找個強有力的隱喻。

讓名稱變成一張張「關係、連接和暗示」的大綱。


交談

當嘗試把物件的用途解釋給別人聽時,就得尋找具有表達力和感染力的名稱。


重要的類別

盡量用一個單字來為它命名。


適當的子類別名稱 (Qualified Subclass Name)

子類別的名稱應該表達出與超類別間的相似性和差異性。


子類別的命名

要描述子類別像什麼,
還要說明它們之間的區別是什麼。


簡短與表達力間的選擇

由於子類別在交談中並不頻繁,所以可以「犧牲簡明」。


例外

如果只是利用繼承來實作貢獻機制,則這樣的子類別也該用簡單的命名。


多層繼承體系的命名

是個難題,一般來說要進行重構,改用委派。

若還是存在,則要思考閱讀者需要知道什麼資訊。


抽象介面 (Abstract Interface)

界面與實作分離。


介面的成本

需要學習它、理解它、替它寫文件、除錯、組織它,甚至替它命名。


經濟的考量

只要一開始設計正確,軟體就不需要進行任何改動?


軟體變更清單

程式設計師沒有弄清需求、客戶改變了想法,

唯一沒有提到的是「正當的變更」。


Interface


多重繼承

一個類別可以實作多個 interface。


介面的命名

如果 interface 叫 File,那實作類別只好叫 ActualFile, ConcreteFile 或 FileImpl。

可以給 interface 加上 I 字首。例如:IFile。


抽象類別 (Abstract Class)

用抽象類別來表現可能會產生變化的抽象界面。


Interface vs Abstract Class

取捨的關鍵,介面會如何變化及實作類別是否需要同時支援多個介面。


使用 Interface 作抽象類別

當修改時,所有的實作類別都必須進行修改。

容易導致現有的設計癱瘓,需借助有版本的 interface。


使用 Abstract Class 作抽象類別

局限在「實作類別必須對其忠心不二」。

如果需要從另一個角度看待同一個實作類別,則只能讓它實作 interface。


兩者並非互斥

可以提供介面,再提供超類別表達一種實作。

在這種情況下,變數宣告應使用介面類型的參考,
這樣將來的維護者就可以根據需要替換。


有版本的 interface (Versioned Interface)

引入 sub-interface,進而安全地對 interface 進行拓展。


1
2
3
interface Command {
void run();
}

1
2
3
interface ReversibleCommand extends Command {
void undo();
}

1
2
3
4
5
6
public void undoIfReversible(Command recent) {
if (recent instanceof ReversibleCommand) {
ReversibleCommand downcasted = (ReversibleCommand) recent;
downcasted.undo();
}
}

使用 instanceof

會降低靈活性,也是個醜陋的解決方案。

但真的遇到這種尷尬局面時,知道該如何修改總是好的。


值物件 (Value Object)

這種物件扮演的角色如同數值一樣。


函數式

永遠不會改變任何狀態,只是建立新的值。


程序式

如果面對的情景總在不斷變化,則狀態式比較適合。


1
2
3
4
5
6
7
8
9
10
11
12
class Transaction {
int value;
Transaction(int value, Account credit, Account debit) {
this.value = value;
credit.addCredit(this);
debit.addDebit(this);
}

int getValue() {
return value;
}
}

效能

使用值物件,最大的反對意見是建立臨時物件。

但考慮整條成本,大部分的情況,都不是效能瓶頸。


邊界

對於「狀態需要變化」與「物件不能變化」的邊界難以劃清。

大部分是值物件,但又不純粹是值物件的風格是最糟糕的。


物件、函數式和程序式

將「使用狀態的可變物件」與「像數值一樣不可變的物件」
搭配一起,能讓程式表達地更好。


特殊化 (Specialization)

描繪相關計算的相似性和差異性。


簡單的變化

abc 與 def 是不同的。

但計算字串長度的演算法相同。


大部分的程式

「相同的邏輯處理不同的資料」和「不同的邏輯處理相同的資料」。


複雜的變化

邏輯完全不同。

例如符號積分程式與數學排版程式。


子類別 (Subclass)

用一個子類別表現一個維度上的變化。


繼承

有些人發現繼承做的就是共用實作的萬靈丹。


繼承的局限

  • 這張牌只能打一次織。
  • 要理解子類別,需先理解超類別。
  • 對超類別的修改充滿風險。

平行的繼承

「這個」繼承體系中的子類別都需要「那個」繼承體系的某個子類別。

這是特別糟糕的繼承用法。


關鍵

把超類別的邏輯徹底劃分,直到每個方法只做一件事。

但當子類別修改超類別方法時,需檢查兩者隱晦的耦合。


子類別的局限

沒辦法表現不斷變化的邏輯,因建立物件時已決定。

如果需要表達邏輯會隨時變化,則條件陳述式或委派更適合。


ʕ •ᴥ•ʔ:Thank you

Share