OO設計原則
在軟體軟體系統中,一個子產品設計得好不好的最主要、最重要的标志,就是該子產品在多大程度上将自己的内部資料和其他與實作有關的細節隐藏起來。一個設計得好的子產品可以将它所有的實作細節隐藏起來,徹底地将提供給外界的API和自己的實作分隔開來。這樣一來,子產品與子產品之間就可以僅僅通過彼此的API互相通信,而不理會子產品内部的工作細節。
OO設計根本的指導原則是提高可維護性和可複用性。這些原則主要有:
<b>1. </b><b>開閉原則</b>
一個軟體實體應該對擴充開放,對修改關閉。
在設計一個子產品的時候,就當使這個子產品可以在不被修改的前提下被擴充。換言之,就當可以在不必修改源代碼的情況下改變這個子產品的行為。
如何做到既不修改,又可以擴充?
解決問題的關鍵在于抽象化:在Java語言裡,可以給出一個或多個抽象Java類或Java接口,規定出所有的具體類必須提供的方法特征作為系統設計的抽象層。這個抽象層預見了所有的可能擴充,是以,在任何擴充情況下都不會改變。這就使得系統的抽象層不需要修改,進而滿足了—對修改關閉。
同時,由于從抽象層導出一個或多個新的具體類可以改變系統的行為,是以系統的設計對擴充是開放的。
開閉原則實際上是對“對可變性的封閉原則“:找到一個系統的可變因素,将之封裝起來。這個原則意昧着兩點:
1) 一個可變性不應當散落在代碼的很多角落裡,而應當被封裝到一個對象裡面。同一種可變性的不同表象意昧着同一個繼承等級結構中的具體子類。
繼承就當被看作是封裝變化的方法,而不應當被認為是從一般的對象生成特殊對象的方法。
2) 一種可變性不應當與另一種可變性混合在一起。(所有類圖的繼承結構一般不會超過兩層,不然就意昧着将兩種不同的可變性混合在了一起。)
開閉原則是總的原則,其它幾條是開閉原則的手段和工具。
<b>2. </b><b>依賴倒轉原則</b>
依賴倒轉原則講的是:要依賴于抽象,不要信賴于實作。
開閉原則是目标,而達到這一目标的手段是依賴倒轉原則。
抽象層次包含的是應用系統的商務邏輯和宏觀的、對整個系統來說重要的戰略性決定,是必然性的展現;而具體層次則含有一些次要的與實作有關的算法和邏輯,以及戰術性的決定,帶有相當大的偶然性選擇。具體層次的代碼是會經常有變動的,不能避免出現錯誤。
抽象層次含有一個應用系統最重要的宏觀商務邏輯,是做戰略判斷和決定的地方,那麼抽象層次就應當是較為穩定的,應當是複用的重點;也應當是維護的重點。
在很多情況下,一個Java程式需要引用一個對象。這個時候,如果這個對象有一個抽象類型的話,應當使用這個抽象類型作為變量的靜态類型。這就是針對接口程式設計的含義。
一般而言,在建立一個對象時,Java語言要求使用new關鍵詞以及這個類本身。而一旦這個對象已經被建立出來,那麼就可以靈活地使用這個對象的抽象類型來引用它。比如:List employees = new Vector();是以,Java語言中建立一個對象的過程是違背“開閉原則”以及依賴倒轉原則的(因為先生成了具體的類型,再使用抽象的引用),雖然在這個類被建立出來以後,可以通過多态性使得用戶端依賴于其抽象類型。正是由于這個問題,設計模式給出了多個建立模式,特别是幾個工廠模式,用于解決對象建立過程中的依賴倒轉問題。
工廠模式将建立一個類的執行個體的過程封裝起來,消費這個執行個體的用戶端僅僅取得執行個體化的結果,以及這個執行個體的抽象類型。當然,任何方法都無法回避Java語言所要求的new關鍵字和直接調用具體類的構造子的做法(這違背了裡氏代換原則)。簡單工廠模式将這個違反“開閉原則”和依賴倒轉原則的做法封裝到了一個類裡面,而工廠方法模式将這個違反原則的做法推遲到了具體工廠角色中。通過适當的封裝,工廠模式可以淨化大部分的結構,而将違反原則的做法孤立到易于控制的地方。
聯合使用Java接口和Java抽象類:聲明類型的工作由Java接口承擔,但是同時給出的還有一個Java抽象類,為這個接口給出一個預設實作。如果一個具體類直接實作這個Java接口的話,它就必須自行實作所有的接口;相反,如果它繼承自抽象類的話,它就可以省去一些不必要的方法,因為它可以從抽象類中自動得到這些方法的預設實作。這其實就是預設适配模式。
<b>依賴倒轉的缺點:</b>
1) 因為依賴倒轉的緣故,對象的建立很可能要使用對象工廠,以避免對具體類的直接引用,此原則的使用還會導緻大量的類。對不熟悉面向對象技術的工程師來說,維護這樣的系統需要較好地面向對象的設計知識。
2) 依賴倒轉原則假定所有的具體類都是會變化的,這也不總是正确的。有一些具體類可能相當穩定、不會發生變化,消費這個具體類執行個體的用戶端完全可以依賴這個具體類型,而不必為此發明一個抽象類型。
<b>3. </b><b>裡氏代換原則</b>
任何基類可以出現的地方,子類一定可以出現。
開閉原則的關鍵步驟是抽象化。而基類與子類的繼承關系就是抽象化的具體展現,裡氏代換原則是對實作抽象化的具體步驟的規範。
<b>4. </b><b>合成/聚合複用原則</b>
要盡量使用合成/聚合,而不是繼承關系達到複用的目的。
合成/聚合原則要求我們首先考慮合成/聚合關系,裡氏代換原則要求在使用繼承時,必須确定這個繼承關系符合一定的條件(繼承是用來封裝變化的;任何基類可以出現的地方,子類一定可以出現。)
合成/聚合原則就是在一個新的對象裡面使用一些已有的對象,使之成為新對象的一部分;新的對象通過向這些對象的委派達到得複用已有功能的目的。
<b>5. </b><b>迪米特原則</b>
一個軟體實體應當盡可能少的其他實體發生互相作用。子產品之間的互動要少。這樣做的結果是當系統的功能需要擴充時,會相對更容易地做到對修改的關閉。
一個對象應當對其他對象有盡可能少的了解。
<b>迪米特原則的具體操作:</b>
1) 優先考慮将一個類設定成不變類。不變類易于設計、實作和使用。比如Java API中的String,BigInteger等類。
一個對象與外界的通信大體上分成兩種,一種是改變這個對象的狀态,另一種是不改變這個對象的狀态的。如果一個對象的内部狀态根本就是不可能改變的,那麼它與外界的通信當然就大大地減少。
當涉及任何一個類的時候,都首先考慮這個類的狀态是否需要改變。即便一個類必須是可變類,在給它的屬性設定指派方法的時候,也要保持吝啬的态度。除非真的需要,否則不要為一個屬性設定指派方法。
2) 盡量降低一個類的通路權限。
3) 謹慎使用Serializable,一旦将一個類設定成Serializable,就不能再在新版本中修改這個類的内部結構,包括private的方法和句段。
4) 盡量降低成員的通路權限。
<b>6. </b><b>接口隔離原則</b>
應當為用戶端提供盡可能小的單獨接口,而不要提供大的總接口。也即是使用多個專門的接口比使用單一的總接口要好。
接口隔離原則與迪米特都是對一個軟體實體與其他的軟體實體的通信限制。迪米特原則要求盡可能地限制通信的寬度和深度,接品隔離原則要求通信的寬度盡可能地窄。這樣做的結果使一個軟體系統在功能擴充過程當中,不會将修改的壓力傳遞到其他對象。
一個接口相當于劇本中的一種角色,而此角色在一個舞台上由哪一個演員來演則相當于接口的實作。是以,一個接口應當簡單地代表一個角色,而不是多個角色。如果系統涉及到多個角色的話,那麼每一個角色都應當由一個特定的接口代表。
定制服務:如果用戶端僅僅需要某一些方法的話,那麼就應當向用戶端提供這些需要的方法,而不要提供不需要的方法。(向用戶端提供public接口是一種承諾,沒有必要做出不必要的承諾,過多的承諾會給系統的維護造成不必要的負擔。)
設計原則是基本的工具,應用這些規則可使代碼更加靈活、更容易維護,更容易擴充。基本原則:封裝變化 Encapsulate what varies. 面向接口變成而不是實作 Code to an interface rather than to an implementation. 優先使用組合而非繼承 Favor Composition Over Inheritan
什麼是設計原則?
設計原則是基本的工具,應用這些規則能夠使您的代碼更加靈活、更容易維護,更容易擴充。
基本原則
封裝變化Encapsulate what varies.
面向接口變成而不是實作 Code to an interface rather than to an implementation.
優先使用組合而非繼承 Favor Composition Over Inheritance
SRP: The single responsibility principle 單一職責
系統中的每一個對象都應該隻有一個單獨的職責,而任何對象所關注的就是自身職責的完成。
Every object in your system should have a single responsibility ,and all the object s services should be focused on carrying out that single responsibility .
每一個職責都是個設計的變因,需求變化的時候,需求變化反映為類職責的變化。當您系統裡面的對象都隻有一個變化的原因的時候,您就已很好的遵循了SRP原則。
假如一個類承擔的職責過多,就等于把這些職責耦合在了一起。一個職責的變化就可能削弱或抑制這個類其他職責的能力。這種設計會導緻脆弱的設計。當變化發生的時候,設計會遭到意想不到的破壞。
SRP 讓這個系統更容易管理維護,因為不是任何的問題都攪在一起。
内聚Cohesion 其實是SRP原則的另外一個名字.您寫了高内聚的軟體其實就是說您很好的應用了SRP原則。
怎麼判斷一個職責是不是個對象的呢?您試着讓這個對象自己來完成這個職責,比如:“書自己閱讀内容”,閱讀的職責顯然不是書自己的。
僅當變化發生時,變化的軸線才具備實際的意義,假如沒有征兆,那麼應用SRP或任何其他的原則都是不明智的。
DRY : Don't repeat yourself Principle
通過抽取公共部分放置在一個地方避免代碼重複.
Avoid duplicate code by abstracting out things that are common and placing those thing in a single location .
DRY 很簡單,但卻是確定我們代碼容易維護和複用的關鍵。
您盡力避免重複代碼候實際上在做一件什麼事情呢?是在確定每一個需求和功能在您的系統中隻實作一次,否則就存在浪費!系統用例不存在交集,是以我們的代碼更不應該重複,從這個角度看DRY可就不隻是在說代碼了。
DRY 關注的是系統内的資訊和行為都放在一個單一的,明顯的位置。就像您能夠猜到正規表達式在.net中的位置相同,因為合理是以能夠猜到。
DRY 原則:怎樣對系統職能進行良好的分割!職責清楚的界限一定程度上確定了代碼的單一性。
OCP : Open-Close Principle開閉原則
類應該對修改關閉,對擴充打開;
Classes should be open for extension ,and closed for modification .
OCP 關注的是靈活性,改變是通過增加代碼進行的,而不是改變現有的代碼;
OCP的應用限定在可能會發生的變化上,通過建立抽象來隔離以後發生的同類變化
OCP原則傳遞出來這樣一個思想:一旦您寫出來了能夠工作的代碼,就要努力確定這段代碼一直能夠工作。這能夠說是個底線。稍微提高一點需要,一旦我們的代碼品質到了一個水準,我們要盡最大努力確定代碼品質不回退。這樣的需要使我們面對一個問題的時候不會使用湊活的方法來解決,或說是放任自流的方式來解決一個問題;比如代碼添加了無數對特定資料的處理,特化的代碼越來越多,代碼意圖開始含混不清,開始退化。
OCP 背後的機制:封裝和抽象;封閉是建立在抽象基礎上的,使用抽象獲得顯示的封閉;繼承是OCP最簡單的例子。除了子類化和方法重載我們更有一些更優雅的方法來實作比如組合;
怎樣在不改變源代碼(關閉修改)的情況下更改他的行為呢?答案就是抽象,OCP背後的機制就是抽象和多态
沒有一個能夠适應任何情況的貼切的模型!一定會有變化,不可能完全封閉.對程式中的每一個部分都肆意的抽象不是個好主意,正确的做法是研發人員僅僅對頻繁變化的部分做出抽象。拒絕不成熟的抽象和抽象本身相同重要。
OCP是OOD很多說法的核心,假如這個原則有效應用,我們就能夠獲更強的可維護性 可重用 靈活性 健壯性 LSP是OCP成為可能的主要原則之一
LSP: The Liskov substitution principle
子類必須能夠替換基類。
Subtypes must be substitutable for their base types.
LSP關注的是怎樣良好的使用繼承.
必須要清楚是使用一個Method還是要擴充他,但是絕對不是改變他。
LSP清楚的指出,OOD的IS-A關系是就行為方式而言,行為方式是能夠進行合理假設的,是客戶程式所依賴的。
LSP讓我們得出一個重要的結論:一個模型假如孤立的看,并不具備真正意義的有效性。模型的有效性隻能通過他的客戶程式來表現。必須根據設計的使用者做出的合理假設來審視他。而假設是難以預測的,直到設計臭味出現的時候才處理他們。
對于LSP的違反也潛在的違反了OCP
DIP:依賴倒置原則
高層子產品不應該依賴于底層子產品 二者都應該依賴于抽象
抽象不應該依賴于細節 細節應該依賴于抽象
什麼是高層子產品?高層子產品包含了應用程式中重要的政策選擇和業務模型。這些高層子產品使其所在的應用程式區分于其他。
假如高層子產品依賴于底層子產品,那麼在不同的上下文中重用高層子產品就會變得十分困難。然而,假如高層子產品單獨于底層子產品,那麼高層子產品就能夠很容易的被重用。該原則就是架構設計的核心原則。
這裡的倒置不但僅是依賴關系的倒置也是接口任何權的倒置。應用了DIP我們會發現往往是客戶擁有抽象的接口,而服務者從這些抽象接口派生。
這就是着名的Hollywood原則:"Don't call us we'll call you."底層子產品實作了在高層子產品聲明并被高層子產品調用的接口。
通過倒置我們建立了更靈活 更持久更容易改變的結構
DIP的簡單的啟發規則:依賴于抽象;這是個簡單的陳述,該規則建議不應該依賴于具體的類,也就是說程式彙總任何的依賴都應該種植于抽象類或接口。
假如一個類很穩定,那麼依賴于他不會造成傷害。然而我們自己的具體類大多是不穩定的,通過把他們隐藏在抽象接口後面能夠隔離不穩定性。
依賴倒置能夠應用于任何存在一個類向另一個類發送消息的地方
依賴倒置原則是實作許多面向對象技術多宣稱的好處的基本底層機制,是面向對象的标志所在。
ISP:接口隔離原則
不應該強迫客戶程式依賴他們無需的使用的方法。
接口不是高内聚的,一個接口能夠分成N組方法,那麼這個接口就需要使用ISP處理一下。
接口的劃分是由使用他的客戶程式決定的,客戶程式是分離的接口也應該是分離的。
一個接口中包含太多行為時候,導緻他們的客戶程式之間産生不正常的依賴關系,我們要做的就是分離接口,實作解耦。
應用了ISP之後,客戶程式看到的是多個内聚的接口。