設計模式(Design pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計
模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多
贏的;設計模式使代碼編制真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。
為什麼要提倡Design Pattern呢?根本原因是為了代碼復用,增加可維護性。那麼怎麼才能實現代碼復用呢?面
向對象有幾個原則:單一職責原則(Single ResponsiblityPrinciple SRP)、開閉原則(Open Closed Principle,OCP)、
裡氏代換原則(LiskovSubstitution Principle,LSP)、依賴倒轉原則(Dependency Inversion Principle,DIP)、接口隔離
原則(Interface Segregation Principle,ISP)、合成/聚合復用原則(Composite/Aggregate Reuse Principle,CARP)、最
小知識原則(Principle ofLeast Knowledge,PLK,也叫迪米特法則)。開閉原則具有理想主義的色彩,它是面向對象
設計的終極目標。其他幾條,則可以看做是開閉原則的實現方法。
設計模式就是實現了這些原則,從而達到了代碼復用、增加可維護性的目的。
學習設計模式之前,首先明確模式是針對面向對象的,它的三大特性,封裝、繼承、多態。面向對象設計模式有
5大基本原則:單一職責原則、開閉原則、依賴倒置原則、接口隔離原則、裡氏代換原則。我們學習的設計模式都是
在面向對象的特性以及5大基本原則的基礎上衍生而來的具體實現,所以學習之前有必要介紹幾大原則的基本概念。
1SRP定義:就一個類而言,應該僅有一個引起它變化的原因。簡而言之,就是功能要單一。
2如果一個類承擔的職責過多,就等於把這些職責耦合在一起,一個職責的變化可能會削弱或者抑制這個類完成
其它職責的能力。這種耦合會導致脆弱的設計,當變化發生時,設計會遭受到意想不到的破壞。(敏捷軟件開發)
3軟件設計真正要做的許多內容,就是發現職責並把那些職責相互分離。(敏捷軟件開發)
4單一職責原則可以看做是低耦合、高內聚在面向對象原則上的引申,將職責定義為引起變化的原因,以提高內
聚性來減少引起變化的原因。職責過多,可能引起它變化的原因就越多,這樣導致職責依賴,相互之間就會產生原
因,大大損傷其內聚性和耦合度。
5不論是在設計類,接口還是方法,單一職責都會處處體現,單一職責的定義:我們把職責定義為系統變化的原
因。所有在定義類,接口,方法的時候。定義完以後再去想一想是不能多於一個的動機去改變這個類,接口,方法。
如果答案是肯定的,說明定義的類,接口,方法則多於一個職責。故違背單一職責,遇到這種情況應該重新細分職
責,直到不會出現多種職責的類,接口方法為止(發現職責,並把那些職責相互分離)。單一職責的為最簡單的五種原
則之一。在軟件設計的過程中處處體現。無處不在。
1OCP的定義:就是說軟件實體(類,方法等等)應該可以擴展,但是不能修改。它是軟件設計中也是最重要的一種
設計原則。
2OCP的兩個特征:
A對於擴展是開放的。
B對於修改是封閉的。
3什麼時候應用OCP原則呢?
在我們最初編寫代碼時,假設變化不會發生,當變化發生時,我們就創建抽象(比如抽象類,接口等等)來隔離以
後發生的同類變化。
4開閉原則是面向對象設計的核心所在。遵循這個原則可以帶來面向對象技術所聲稱的巨大好處,也就是可維
護,可擴展,可復用,靈活性好。開發人員應該僅對程序中呈現出頻繁變化的那些部分做出抽象,然而,對於應用程
序中的每個部分都刻意地進行抽象同樣不是一個好主意。拒絕不成熟的抽象和抽象本身一樣重要。
5開閉原則是指類、模塊、方法是可以擴展的,但不可以修改。開即對擴張開放,閉即對修改關閉。開閉原則的
應用體現在,開發人員應該僅僅對程序中頻繁出現變化的地方進行抽象(封裝變化點)。對變化點的封裝即對變化的修
改關閉。對於變化的不確定性,可隨時擴展。即繼承的使用。抽象類的運用。
6OCP的UML圖:
1LSP的定義:子類型必須能夠替換掉它們的父類型。簡單地說,這是因為子類型繼承了父類,所以子類可以以
父類的身份出現。
2任何基類可以出現的地方,子類一定可以出現。 LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件
單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。裡氏代換原則是對
開閉原則的補充。實現開閉原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以裡氏
代換原則是對實現抽象化的具體步驟的規范。
3替換原則的實現。對於一組具有類似屬性,方法,變量的類。我們可以提取公共屬性,方法,變量做為一個基
類(抽象類或者類),使這一組類繼承基類,重寫虛方法。現在這些繼承的類和基類的關系符合Is-A。如基類為動物,則
繼承類可以為狗,貓。我們可以說貓Is-A動物,狗Is-A動物。
4實例UML圖:
實現的Java代碼:
//Animal父類 class Animal{ public void eat() { } public void drink() { } public void run() { } public void shout() { } } //Cat子類 class Cat extends Animal{ } //Dog子類 class Dog extends Animal{ } //Cattle子類 class Cattle extends Animal{ } //Sheep子類 class Sheep extends Animal{ } public class Test{ public static void main(String[] arge){ Animal animal = new Cat();//根據需求的變化,這裡可以替換成Dog,Cattle或Sheep,程序其它地方不需要改變 animal.eat(); animal.drink(); animal.run(); animal.shout(); } }
5總結:項目中所有使用子類的地方都可用父類替換,但在調用方法的時候 ,即呈現面向對象編程的多態性。即
裡氏替換原則,非常重要的原則,也是比較對難的原則。
1DIP的定義:抽象不應該依賴細節,細節應該依賴於抽象。高層模塊不應該依賴於低層模塊,二者都應該依賴於
抽象。簡單說就是,我們要針對接口編程,而不要針對實現編程。
2反面例子UML圖:
缺點:高層模塊太依賴低層模塊,耦合太緊密。低層模塊發生變化會影響到高層模塊。
解決方法:利用依賴倒置原則使高層模塊和低層模塊都依賴於抽象(接口或抽象類)。
修改後的UML圖如下:
優點:這樣的話修改低層模塊不會影響到高層模塊,減小了它們之間的耦合度,增強系統的穩定性。3在面向過程的開發語言中分析和設計,總是創建一些高層模塊去調用低層模塊、策略依賴於細節的軟件結構。
實際上這種方法的目的就是要定義子程序層次結構,該結構描述了高層模塊怎樣調用低層模塊。而設計良好的面向對
象的程序,正好“倒置”了這種依賴關系高層模,塊不再依賴於低層模塊,從而低層模塊的修改不會影響到高層模塊,
並且高層模塊也是能非常容易的被重用,高層模塊和低層模塊都影響都依賴於抽象。這樣也非常符合強內聚松耦合的
編程思想。故該原則也是框架設計的核心原則。
使用傳統的過程化程序設計所創建出來的依賴關系結構,策略是依賴於細節的,這是糟糕的,因為這樣會使策略
受到細節改變的影響,面向對象的程序設計倒置了依賴關系結構,全程細節和策略都依賴抽象,並且常常是客戶程序
擁有服務接口。事實上,這種依賴關系的倒置正是好的面向對象設計 的標志所在,使用何種語言來編寫程序是無關緊
要的。如果程序的依賴關系是倒置的,它就是面向對象的設計。如果程序的依賴關系不是倒置的,它就是過程化的設
計。
4依賴倒置原則是實現許多面向對象技術所宣稱的好處的基本低層機制。它的正確應用對於創建可重用的框架來
說是必需的。同時它對於構建在變化面前富有彈性的代碼也是非常重要的,由於抽象和細節彼此隔離,所以代碼也非
常容易維護。
5總結:依賴倒置原則其實可以說是面向對象設計的標志,用哪種語言來編寫程序不重要,如果編寫時考慮的都
是如何針對抽象編程而不是針對細節編程,即程序中所有的依賴關系都是終止於抽象類或者接口,那就是面向對象的
設計,反之那就是過程化的設計了。
1ISP的定義:定制服務的例子,每一個接口應該是一種角色,不多不少,不干不該干的事,該干的事都要干。
2使用多個專門的接口比使用單一的總接口要好。一個類對另外一個類的依賴性應當是建立在最小的接口上的。
一個接口代表一個角色,不應當將不同的角色都交給一個接口。沒有關系的接口合並在一起,形成一個臃腫的大接
口,這是對角色和接口的污染。不應該強迫客戶依賴於它們不用的方法。接口屬於客戶,不屬於它所在的類層次結
構。這個說得很明白了,再通俗點說,不要強迫客戶使用它們不用的方法,如果強迫用戶使用它們不使用的方法,那
麼這些客戶就會面臨由於這些不使用的方法的改變所帶來的改變。
3應該說該原則是處理現有“胖”接口所存在的缺點。如果類的接口不是內聚的,就表示該類具有“胖”接口。換句話
說“胖”接口可以分解成多組方法。每一組方法都服務於一組不同的客戶程序。這樣,量引客戶程序可以使用一組成員
函數,而其他客戶程序可以使用其他組的成員函數。
4接口隔離的方法有兩種(分享客戶就是分離接口):
A使用委托(此委托非.net委托[delegate])分離接口。
使用委托即創建一個委托類,用此類去實現分離後的其它接口中的方法。
B使用多重繼承分離接口、此方法,即將現有“胖”接口分成供不同客戶程序調用的兩個或多個接口,而需要實現多個接口的客戶程序,則使
用多重繼承來實現。這兩種方法是實現接口隔離的全部方法,其中第二種方法使用較普遍,也比較簡單。而第一種方
法使用起來相對比較復雜,而且在使用委托的過程中也會產生重復的對象,則占用運行時間和內存開銷。有的時候第
二種方法是必須的,第一種方法是不能使用的。如:利用委托對象所做的轉換是必需的,或者不同的時候會需要不同
的轉換。
(1)面向對象最終的設計目標:
A可擴展性:有了新的需求,新的性能可以容易添加到系統中,不影響現有的性能,也不會帶來新的缺陷。
B靈活性:添加新的功能代碼修改平穩地發生,而不會影響到其它部分。
C可替換性:可以將系統中的某些代碼替換為相同接口的其它類,不會影響到系統。
(2)設計模式的好處:
A設計模式使人們可以更加簡單方便地復用成功的設計和體系結構。
B設計模式也會使新系統開發者更加容易理解其設計思路。
(3)學習設計模式有三重境界:
第一重: 你學習一個設計模式就在思考我剛做的項目中哪裡能用到(手中有刀,心中無刀)
第二重: 設計模式你都學完了,但是當遇到一個問題的時候,你發現有好幾種設計模式供你選擇,你無處下(手
中有刀,心中也有刀)
第三重:也是最後一重,你可能沒有設計模式的概念了,心裡只有幾大設計原則,等用到的時候信手拈來(刀法
的最高境界:手中無刀,心中也無刀)