軟件的特點
1.軟件需求變更法則
軟件不斷變更法則:真實世界中使用的程序必須進行變更,否則它在環境中的作用就會越來越小.
2.增加一個功能特性的成本
(1)原有代碼的理解成本
(2)設計修改成本(編碼)
(3)測試成本
(4)發布成本
3.對軟件需求變化的態度
通過提高團隊的能力,設計的彈性,流程的靈活性來適應變化.
專注於團隊和個人的技能和才華來擁抱變化而不是試圖拒絕變化.
4.拙劣軟件的表現
僵化性:設計難以改變。很難對系統進行改動,因為每個改動都會迫使系統其他部分的改動。如果單一的改動也會導致有依賴關系的模塊連鎖改動,那么設計就是僵化的,必須要改的模塊越多,設計越僵化。
脆弱性:設計易遭到破壞。對系統的改動會導致系統中和改動概念上無關的許多部分出現問題。在進行一個改動時,程序就會可能出現問題,出現問題的地方和改動的地方常常沒有概念上的關聯,要修正這些問題又會引入新的問題。
牢固性:設計難以重用。很難解開系統糾結,使之成為一些其它系統中重用的組件。設計中包含了對其它系統有用的部分,但是把這些部分從系統中分離出來需的努力和風險是具大的。
粘滯性: 難以做正確的事情。做正確的事情比做錯誤的事件要困難。軟件粘滯性:當面臨一個改動時,開發人員會有多種改動方法,其中一些方法會保持設計,而另一些方法會破壞設計,當保持設計比破壞設計更難應用時,就說明設計具有很高的粘滯性。環境粘滯性:開發環境遲鈍低效產生,比如編譯很慢,就會引誘開發人員去做不會導致大規模重新編譯的改動。
不必要的復雜性:過分設計。設計中包含有不具任何好處的基礎數據結構。如果設計中包含了當前沒有用的組成部分,它就含有不必要有復雜性。開發人員預測可能的需求變化,從而放置了潛在變化的代碼,以保持靈活性,看起來是好事,但結果常常正好相反,致使設計中含有不絕不用到的結構,使軟件變得復雜,難於理解。
不必要的重復:設計中包含重復的結構,而該結構本可以使用單一的抽象體進行統一。當同樣的代碼以稍微不同的形式反復出現時,就表示開發人員忽略了抽象。當系統中有重復的代碼時,對系統的改動會變得困難,重復的錯誤必須在每個重復體中一一修正,但是重復的代碼之間有細微的差別,修正的方式也不總是相同。
晦澀性:很難閱讀理解,沒有很好的表現意圖。代碼隨着時間的變化,會變得越來越晦澀,為了使代碼晦澀性保持最低,就需要持續的保持代碼清晰並具有表現力。當開發人員編寫代碼時,代碼對於他們來說也許是清晰的,這是由於他們使自己專注於代碼的編寫,並且他們對代碼非常熟悉,當熟悉退化后,他們或許回過頭來再去看那個模塊,他們會想怎么會有如些糟糕的代碼,為了防止這種情況,開發人員必須站在閱讀者的角度,對代碼進行重構。
5.優秀設計的目標
可擴展性:新的功能可以很容易的加入到系統之中
靈活性:可以容許代碼平穩修改,而不會波及到很多其它的模塊
可插入性:可以很容易的將一個類抽出去,同時將一個有同樣接口的類加入進來
6.優秀設計
(1)發現變化/封裝變化
(2)隔離變化
(3)動態綁定
(4)解耦具體實現
設計原則
1.單一職責原則(變化分離)
就一類而言,應該有且只有引起它變化的原因.
如果你能夠想到多於一個的動機去改變一個類,那么這個類就具有多於一個的職責.
2.里氏替換原則(解釋如何進行繼承)
基類可以出現的地方都可以用子類進行替換,而不會引起任何不適應的問題.
里氏替換原則是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為.
里氏代換原則是對“開-閉”原則的補充.實現“開-閉”原則的關鍵步驟就是抽象化.
覆蓋或實現父類的方法時,子類返回值類型可以是父類返回值類型的子類.
3.依賴倒置原則(針對接口編程)
高層模塊不應該依賴於低層模塊,他們都應該依賴於抽象;抽象不應該依賴於具體實現,具體實現應該依賴於抽象.
(低層模塊,不可分隔的原子邏輯即為低層模塊;高層模塊,原子邏輯的再組裝形成高層模塊)
針對接口編程,依賴於抽象而不依賴於具體.降低客戶與實現模塊間的耦合.
4.接口隔離原則(恰當的划分角色和接口)
客戶端不應該依賴於它不需要的接口;類間的依賴關系應該建立在最小的接口上.
因此,使用多個隔離接口比使用單個接口好.
一個接口代表一個角色,不應當將不同的角色都交給一個接口.
沒有關系的接口合並在一起,形成一個臃腫的大接口,這是對角色和接口的污染.
不應該強迫客戶依賴於它們不用的方法.
接口屬於客戶,不屬於它所在的類層次結構.
5.迪米特法則(最小知道原則)
一個對象應該對其它對象有最小的了解.
一個軟件實體應盡可能少的與其它實體發生相互作用,使得系統模塊功能相對獨立.
每一個軟件單位對其它的單位都只有最少了解,而且局限於那些與本單位密切相關的軟件單位.
類成員函數應該只調用以下對象:
對象自身,函數參數,成員對象,成員集合中的元素,函數中創建的對象.
6.開閉原則(可變性封裝)
軟件組成實體(類,模塊,函數等)應該是可擴展的,但是是不可修改的.即軟件實體可以通過增加新代碼實現擴展,但是不能修改已有的代碼.
任何系統在其生命周期中都會發生變化,如果我們希望開發出的系統不會在第一版發布后就被拋棄,就必須牢記這一點.
把會變化的部分抽取並封裝起來,以便以后可以輕易地改動和擴充,而不影響不需要變化的其它部分.
禁忌:
有些時候使用多態設計形成的變化點在實際中不一定會發生或從未發生過,因此這種付出是不必要的.
在投入靈活的設計前,一定要現實地對待可變性的真實可能性.不要過度設計!
設計原則實踐運用
1發現變化/封裝變化
找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起.
2抽象穩定接口,針對接口
抽象接口:創建出固定卻能描述一組任意個可能行為的抽象體,這個抽象體就像抽象基類或接口,任意個可能的行為就是可能的派生類.
針對接口(抽象)設計:由於子模塊間依賴於一個固定的抽象,所以它對於更改可以是關閉的.
3合成復用原則
盡量使用動態綁定(組合/聚合),而不是繼承關系達到復用的目的.
在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分,新的對象通過向這些對象的委派達到復用已有功能的目的.
4靜態綁定與動態綁定
靜態綁定:代碼結構在編譯時刻就被確定下來了,比如由繼承關系固定的類組成,或者硬編碼實現等.
動態綁定:程序運行時刻的結構是由快速變化的對象實現,編譯時兩個結構是彼此獨立的.
5創建與使用分離
對象要么構造對象,要么使用對象,而不應該兼而有之.如果遵守這一約束,就能加強內聚和降低耦合.
如果你吃了一只雞蛋覺得味道不錯,難道一定要見到那只母雞?
6針對接口編程
(1)每個類盡量都有接口或抽象類,或兩者兼有.這是依賴倒置原則的基本要求,有了抽象才能依賴倒置.
(2)變量的表面類型盡量是接口或抽象類.
(3)任何類都不應該從具體類派生.
(4)盡量不重寫基類的方法.
(5)接口負責public屬性和方法,並且聲明與其它對象之間的依賴關系;抽象類負責公共構造部分的實現;實現類准確實現業務邏輯.
7告訴而不是詢問
應該盡量告訴對象去做的事情,而不要詢問它們的狀態之后做出決定,最后才告訴它們做什么事情.對象擁有了數據,就應該讓它們自己去完成.
8過程式設計與面向對象設計
過程式程序獲取信息然后決策,面向對象則告訴對象做某事件.
過程式設計(使用數據結構)便於在不改動既有數據結構的前提下添加新函數.因此,過程化代碼難以添加新的數據結構,因為必須修改所有函數.
面向對象設計便於在不改動既有函數的前提下添加新類.因此,難以添加新函數,因為必須修改類.
在復雜系統中,需要添加新數據類型而不是新函數的時候,適合面向對象;需要添加新函數而不是新類型的時候,適合過程代碼和數據結構結合的方式.