【閱讀筆記】軟體構築美學(07)在專案中導入好的物件導向實務

複習物件導向語言的基礎觀念。運用物件導向原則來維護應用程式。其他有關維護城市的實務技巧。

書名:軟體構築美學

作者:Kyle Baley,Donald Belcham

譯者:蔡煥麟,張簡才祿

發行公司:悅知文化,精誠資訊股份有限公司

物件導向的基礎觀念

  • 封裝(encapsulation)
  • 繼承(inheritance)
  • 抽象化(abstraction)
  • 多型(polymorphism)

封裝

一種防止外界(呼叫端)存取物件內部實作細節的手段。

當你呼叫某物件的 FindDocumentsNamed(name) 方法時,你並不需要瞭解它實際上是怎麼找到哪些文件的,重點在於它是否完成了它該做的事。

 

繼承

以既有類別為基礎,因而衍生成特殊版本的機制,這些特殊化的子類別,它們繼承了父代類別的屬性與方法,而且通常會提供額外的行為,或改寫既有的行為。

繼承經常被濫用,所以保持繼承結構的簡單才是最重要的。

 

抽象化

它是簡化複雜事物的方法,就像一個物件、一群物件、或一組服務。

許多好用的物件導向原則皆源自於此。

 

多型

多型與繼承有關。

我們知道可以從基礎類別衍生出子類別,多型則能夠讓我們把子類別的物件當作基礎類別來用。

 

好程式的能力

好維護應用程式所應具備的五種能力

  • 可讀性
  • 可測試性
  • 擴充性
  • 可逆性
  • 調適性

 

可維護性

無論何時,你都應該自問:『下一個接手維護此程式的人,是否能夠迅速了解這些程式在做些什麼?』

並且牢記,這下一個人,可能就是一年後的你。

Damian Conway 在《 Perl Best Practices 》一書中說,『寫程式時,請想像最後維護此程式的人,是個有暴力傾向的精神病患,而且他知道你住哪裡』。

程式碼大多是處於維護的狀態,讓程式碼更容易維護是最終目的。

 

可讀性

為了確保程式碼容易閱讀。

花時間將含混不清的變數重新命名,就能提高程式碼的可讀性。

同樣的觀念亦適用於方法與屬性名稱。

DoCalc( ) 不如寫 CalculateMinimumTemperature( ) 還來得清楚明白。

串接介面(fluent interface):該介面的方法會互相串接,讓程式碼更容易理解。

imageTransformer
    .Form( selectedImage )
    .Using( pixelsPerInch )
    .With( bilinearResampling )
    .ResizeTo( width, height );

 

可測試性

如果你曾經試著為程式撰寫自動化測試,就會發現可測試的設計多麽重要了。

mock 物件:可以確保類別只依賴那些能夠模擬的介面,如此一來,當我們在進行測試的時候,便能夠將注意力集中在受測類別的行為上。

 

擴充性

你應該盡可能讓程式容易擴充。

雖然你不知道程式碼將來會怎麼用,也無須考慮每一種可能情況,但你應該讓程式碼在需要加入新行為時很容易修改。

 

可逆性

設計程式時,讓任何設計決策都能輕易回復的一種概念。

如果你的程式是可回復的,就不用想那麼遠、不用顧及所有細節,而任何有爭議的地方也都可以『等到必要時再修改』的方式來應對。

 

調適性

我們可以確定,程式碼一定會不斷修改,有時是因為需求的變更、有時是修正臭蟲、或加入新功能,甚至因為升級 .NET framework 版本而必須改變某些程式的作法。

在撰寫程式時,你經常想著日後的維護問題,並確保變動發生時不用大幅度重寫。

協助達成調適性的設計模式:調適器(Adapter)與門面(Facade)

Adapter 模式:用來將一個介面轉換成另一個介面,其運用時機,是當用戶端需要的是某個介面,但與之連接的系統卻提供了不同的介面。

Facade 模式:將一組介面簡化成一個單一介面,好讓用戶關更容易使用。

 

好的物件導向原則

書目推薦:Rober C.Martin 與 Micah Martin 合著的《Agile Principles, Patterns, and Practices in C#》

 

優先考慮介面組合,而非繼承

我們往往使用了繼承,避免了多餘的重複,並為此感到自豪。

但繼承只是一種短期的解決方法,因為常會造成太過龐大、深層的繼承結構,令程式碼難以維護。

一旦修改了上層的基礎類別,就會產生牽一髮動全身的連鎖反應。

使用介面,我們可以輕易的設計新的行為,並賦予物件所需的相關行為,介面組合的方式提供了更大的彈性。

 

關注點分離 / 單一職責

  • 單一職責(SRP:single reponsibility principle):當一個類別需要修改時,應該只有一個理由。
  • 關注點分離(Soc:separation of concerns):把應用程式的功能分解成數個彼此不相疊的模組。

單一職責與棕地應用程式

應用程式盡量改成符合 SRP ,會讓程式更好維護。

若每個類別都只負責一項功能,程式碼會更簡潔,而更容易管理。

你會發現類別數量突然增加了好多,但低耦合度、高內聚力的應用程式所產生的效益,絕對遠大於檔案數量太多所帶來的麻煩。

基本上,單一職責的類別若需要修改,肯定就只有一個原因,而當此唯一的原因出現,我們就很容易能判斷要修改哪個地方。

 

開放 / 封閉原則

它直接牽涉到如何穩定一個應用程式,並且也聲明『軟體本身(包含類別、模組、函式等等)應該是具備開放且可以擴展,但需要封閉的去修改它』。

策略模式(Strategy pattern):將篩選條件的演算法封裝成一個介面,以便在執行期間能夠任意切換篩選條件。

模板方法(Template Method):牽扯到抽象類別以及方法的建立,並且方法會去呼叫抽象方法或虛擬方法,所以會取決於子類別如何實作或覆寫父類別的抽象/虛擬方法,以便 Template Method 可以妥善的運作。

Decorator 模式(Decorator pattern):包裝另一類別,並會為它增加一些行為,注意這不同於父類別的繼承關係,Decorator 會實作相同的介面(將它視為父類別),通常會將修飾用的物件,透過建構子傳入 Decorator 內。

開放 / 封閉原則與棕地應用程式

若忽略開放 / 封閉原則,會讓類別變成擁擠、雜亂無章,並且導致難以測試以及維護。

要讓類別可以同時具備開放、可擴充性,以及保有封閉的修改特性,這樣並不是一件很困難的事,一般的經驗法則是:寫(重構)成介面

透過介面的幫助,我們可以簡單地加入新功能,並且不需要去重新編輯程式碼。

 

最少知識原則

最少知識原則( principle of least knowledge)又稱為迪米特法則(Law of Demeter)

簡單來說就是:『只和你直接有相關的朋友溝通』。

最少知識原則與棕地應用程式

MyObject.MyCollection.Add(something);

潛藏了一個隱憂,如果我們現在改變了 MyCollection 屬性的回傳型別,可能就沒有 add 可以使用了

MyObject.AddSomething(something);

public void AddSomething(Something itemToAdd)
{ 
   _myCollection.Add(itemToAdd); 
}

現在如果我們要改變底層的集合型別,要改變加入項目的方式,此時只要專注修改 AddSomething 方法上的程式碼。

最少知識原則是一個很好的做法,可以幫助我們隔離改變所造成的影響。

 

依賴反轉原則

高階層模組不應該依賴低階層模組,兩者應該依賴在一個抽象層上。

抽象層不應該依賴實作細節,而是實作細節應該依賴抽象層。

 

以介面為基礎的設計

『有人會負責寫程式碼(服務提供者),有人則計畫去呼叫程式碼(客戶端),並透過一個彼此同意的介面來呼叫方法,此時,用戶端只需在方法上傳入適當的參數,服務提供者就會保證產生一個特定的結果』

以介面為基礎的設計是測試驅動設計的核心概念。(建立程式碼之前,先寫測試程式)

上述的測試驅動方式,允許我們從呼叫者的觀點來檢視我們的介面,客戶端使用上會更加方便。

以介面為基礎的設計與棕地應用程式

將公開的方法,組合成一個介面,進行一些隔離的處理,接下來,所有用到此程式碼的地方,將它改變成使用介面,而非原來的具體類別。

這樣的改變,可以降低呼叫程式碼和原先實際類別之間的耦合,同時讓我們更容易來進行重構。

 

在團隊上採用設計原則

做一個雛型(prototype)

雛型能夠讓我們更容易地掌握住觀念,並且可以快速的讓我們實驗新功能或想法。

使用分支:確保製作雛型過程中沒有結果,還能夠還原回原先的狀態。

時間管理:在開始之前,要先設定一個合理的時間。

午餐學習(Lunch N Learn)

透過午餐時間,一起討論設計模式,並準備好範例程式,引導大家發表意見,讓大家以討論的方式實際了解。

師徒方式

若要快速提升某些人的技術,透過師徒方式會相當的有用,你和另一個人一同工作,可以和對方學習一些實作方式或實務經驗。

Pair-Programming:角色相同,也就是開發人員,主要目的是一起解決問題。

師徒模式:老師與學生的關係,主要目的是為了教導。

可以快速教導開發人員的技術能力,同時,你也可以提供一些個人意見,或是指出問題所在,並且可以因材施教。

由你來重構

你可以不用去努力的將設計原則引入進來,但可以當加入一個新功能或是修改臭蟲時,邊開始進行重構,並且將原則也慢慢引入。

鼓勵你的團隊去進行重構,並且在開始進行重構時,你可以透過午餐學習、師徒方式來提供一些指導。

 

採用更敏捷的方式

為了要更敏捷,我們的程式碼也要能夠快速回應改變。

 

保持簡單(Keep it simple, stupid:KISS)

讓程式碼簡潔清楚,當別人需要對這支程式修改時,可以讓他們很容易理解。

過早最佳化(premature optimization)來降低專案的複雜度

定義:『當加入一段可以提升效能的程式碼時,我們不會在一開始就去衡量他是否會帶來一些問題』。

奧卡姆剃刀(Occam's razor):『若所有的事情都是相等的,最簡單的做法就是最好的選擇』。

 

你將不需要它(You ain't gonna need it:YAGNI)

定義:『我們不應該為程式碼加入未用的功能,除非我們需要用到』。

開發人員常常會幻想自己是預言家,加入一些尚未用到的方法或是類別,就只為了以防萬一。

 

不要做重複的事情(Don't repeat yourself:DRY)

定義:『我們要不惜代價來避免重複的程式碼』

正如我們最喜歡的剪下貼上,這將會導致一些長期的問題。

 

總結

最終目標都是為了讓系統更好維護。

好的物件導向原則,需要深入研究並帶到團隊內。