複習物件導向語言的基礎觀念。運用物件導向原則來維護應用程式。其他有關維護城市的實務技巧。
書名:軟體構築美學
作者: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)
定義:『我們要不惜代價來避免重複的程式碼』
正如我們最喜歡的剪下貼上,這將會導致一些長期的問題。
總結
最終目標都是為了讓系統更好維護。
好的物件導向原則,需要深入研究並帶到團隊內。