模式:每個模式描述了一個在咱們周圍不斷重複發生的問題,以及該問題的解決方案的核心。」
這是關於模式最經典的定義,做者是建築大師Christopher Alexander。若是是第一次看到這句話,多數人會以爲有些抽象難懂。其實「模式」兩個字只是一個代號,就像個人英文名字叫Justin,若是我改叫Tom也沒什麼問題,只是我更喜歡Justin這個名字,因此從Christopher開始,有了「模式」這個詞,人們也都把關於「重複發生的問題的描述和解決辦法」統稱爲模式。
「模式」這個詞是不侷限於軟件開發行業的,它幾乎無處不在,它其實就是一種經驗的積累,就象大多數人的教育經歷都是從小學到初中再到高中再到大學,這也是一種模式,是中國的教育模式;如今愈來愈火的出國熱,也是另外一種模式:海外留學模式。由於GOF的《設計模式:可複用面向對象軟件的基礎》一書描述的23種經典設計模式,奠基了模式在軟件行業的地位,今後人們提到「設計模式」就是默指「面向對象設計模式」,可是如前文所述,模式絕對不侷限於軟件行業,即便在軟件行業,也不侷限於GOF描述的23種設計模式,例如最著名的Martin Flower的《企業架構模式》,還有咱們經常使用的MVC、IOC等架構模式。
由於模式是一種經驗的積累和總結,因此經過模式,咱們能夠站在巨人的肩膀上去思考問題、解決問題,熟練使用設計模式能夠提升咱們的工做效率,改善產品質量,最終帶來經濟效益。所以對於任何想開發出靈活高效、健壯的軟件產品的我的或團體,熟練掌握並正確使用設計模式都是必須掌握的基本技能。數據庫
要學習設計模式,有些基礎知識是咱們必需要先知道的,設計模式是關於類和對象的一種高效、靈活的使用方式,也就是說,必須先有類和對象,纔能有設計模式的用武之地,不然一切都是空談,那麼類和對象是從那冒出來的呢?這時就須要比23種設計模式更重要更經典的GRASP模式登場了。編程
GRASP,全稱爲General Responsibility Assignment Software Pattern,即通用職責分配軟件模式,它由《UML和模式應用》(Applying UML and Patterns)一書做者Craig Larman提出。與其將它們稱之爲設計模式,不如稱之爲設計原則,由於它是站在面向對象設計的角度,告訴咱們怎樣設計問題空間中的類與分配它們的行爲職責,以及明確類之間的相互關係等,而不像GoF模式同樣是針對特定問題而提出的解決方案。所以GRASP站在一個更高的角度來看待面向對象軟件的設計,它是GoF設計模式的基礎。GRASP是對象職責分配的基本原則,其核心思想是職責分配(Responsibility Assignment),用職責設計對象(Designing Objects with Responsibilities)。它包含以下9個基本特徵或原則:設計模式
(1) 問題:給對象分配職責的通用原則是什麼?服務器
(2) 解決方案:將職責分配給擁有履行一個職責所必需信息的類,即信息專家。架構
(3) 分析:信息專家模式是面向對象設計的最基本原則。通俗點來說,就是一個類只幹該乾的事情,不應乾的事情不幹。在系統設計時,須要將職責分配給具備實現這個職責所須要信息的類。信息專家模式對應於面向對象設計原則中的單一職責原則。ide
例如:常見的網上商店裏的購物車(ShopCar),須要讓每種商品(SKU)只在購物車內出現一次,購買相同商品,只須要更新商品的數量便可。以下圖:模塊化
針對這個問題須要權衡的是,比較商品是否相同的方法須要放到那裏類裏來實現呢?分析業務得知須要根據商品的編號(SKUID)來惟一區分商品,而商品編號是惟一存在於商品類裏的,因此根據信息專家模式,應該把比較商品是否相同的方法放在商品類裏。函數
(1) 問題:誰應該負責產生類的實例?學習
(2) 解決方案:若是符合下面的一個或者多個條件,則可將建立類A實例的職責分配給類B:spa
此時,咱們稱類B是類A對象的建立者。若是符合多個條件,類B聚合或者包含類A的條件優先。
(3) 分析:若是一個類建立了另外一個類,那麼這兩個類之間就有了耦合,也能夠說產生了依賴關係。依賴或耦合自己是沒有錯誤的,可是它們帶來的問題就是在之後的維護中會產生連鎖反應,而必要的耦合是逃不掉的,咱們能作的就是正確地建立耦合關係,不要隨便創建類之間的依賴關係,那麼該如何去作呢?就是要遵照建立者模式規定的基本原則,凡是不符合以上條件的狀況,都不能隨便用A建立B。建立對象是面向對象系統中最廣泛的活動之一,所以,肯定一個分配建立對象的通用職責很是重要。若是職責分配合理,設計就能下降耦合,提升設計的清晰度、封裝性和重用性。一般狀況下,若是對象的建立過程不是很複雜,則根據上述原則,由使用對象的類來建立對象。可是若是建立過程很是複雜,並且可能須要重複使用對象實例或者須要從外部注入一個對象實例,此時,能夠委託一個專門的工廠類來輔助建立對象。建立者模式與各類工廠模式(簡單工廠模式、工廠方法模式和抽象工廠模式)相對應。例如:由於訂單(Order)是商品(SKU)的容器,因此應該由訂單來建立商品。以下圖:
這裏由於訂單是商品的容器,也只有訂單持有初始化商品的信息,因此這個耦合關係是正確的且沒辦法避免的,因此由訂單來建立商品。
(1) 問題:怎樣支持低的依賴性,減小變動帶來的影響,提升重用性?
(2) 解決方案:分配一個職責,使得保持低耦合度。
(3) 分析:耦合是評價一個系統中各個元素之間鏈接或依賴強弱關係的尺度,具備低耦合的元素不過多依賴其餘元素。此處的元素能夠是類,也能夠是模塊、子系統或者系統。具備高耦合的類過多地依賴其餘類,這種設計將會致使:一個類的修改致使其餘類產生較大影響;系統難以維護和理解;系統重用性差,在重用一個高耦合的類時不得不重用它所依賴的其餘類。所以須要對高耦合的系統進行重構。
類A和類B之間的耦合關係體現以下:A具備一個B類型的屬性;A調用B的方法;A的方法包含對B的引用,如方法參數類型爲B或返回類型爲B;A是B的直接或者間接子類;B是一個接口,A實現了該接口。低耦合模式鼓勵在進行職責分配時不增長耦合性,從而避免高耦合可能產生的不良後果。在進行類設計時,須要保持類的獨立性,減小類變動所帶來的影響,它一般與信息專家模式和高內聚模式一塊兒出現。爲了達到低耦合,咱們能夠經過以下方式對設計進行改進:
例如:Creator模式的例子裏,實際業務中須要另外一個出貨人來清點訂單(Order)上的商品(SKU),並計算出商品的總價,可是因爲訂單和商品之間的耦合已經存在了,那麼把這個職責分配給訂單更合適,這樣能夠下降耦合,以便下降系統的複雜性。以下圖:
這裏咱們在訂單類裏增長了一個TotalPrice()方法來執行計算總價的職責,沒有增長沒必要要的耦合。
(1) 問題:怎樣使得複雜性可管理?
(2) 解決方案:分配一個職責,使得保持高內聚。
(3) 分析:內聚是評價一個元素的職責被關聯和關注強弱的尺度。若是一個元素具備不少緊密相關的職責,並且只完成有限的功能,則這個元素就具備高內聚性。此處的元素能夠是類,也能夠是模塊、子系統或者系統。
在一個低內聚的類中會執行不少互不相關的操做,這將致使系統難於理解、難於重用、難於維護、過於脆弱,容易受到變化帶來的影響。所以咱們須要控制類的粒度,在分配類的職責時使其內聚保持爲最高,提升類的重用性,控制類設計的複雜程度。爲了達到低內聚,咱們須要對類進行分解,使得分解出來的類具備獨立的職責,知足單一職責原則。在一個類中只保留一組相關的屬性和方法,將一些須要在多個類中重用的屬性和方法或完成其餘功能所需的屬性和方法封裝在其餘類中。類只處理與之相關的功能,它將與其餘類協做完成複雜的任務。
例如:一個訂單數據存取類(OrderDAO),訂單便可以保存爲Excel模式,也能夠保存到數據庫中;那麼,不一樣的職責最好由不一樣的類來實現,這樣纔是高內聚的設計,以下圖:
這裏咱們把兩種不一樣的數據存儲功能分別放在了兩個類裏來實現,這樣若是將來保存到Excel的功能發生錯誤,那麼就去檢查OrderDAOExcel類就能夠了,這樣也使系統更模塊化,方便劃分任務,好比這兩個類就能夠分配個不一樣的人同時進行開發,這樣也提升了團隊協做和開發進度。
(1) 問題:誰應該負責處理一個輸入系統事件?
(2) 解決方案:把接收或者處理系統事件消息的職責分配給一個類。這個類能夠表明:
(3) 分析:一個控制器是負責接收或者處理系統事件的非圖形用戶界面對象。一個控制器定義一組系統操做方法。在控制器模式中,要求系統事件的接收與處理一般由一個高級類來代替;一個子系統須要定義多個控制器,分別對應不一樣的事務處理。一般,一個控制器應當把要完成的功能委託給其餘對象,它只負責協調和控制,自己不完成太多的功能。它能夠將用戶界面所提交的請求轉發給其餘類來處理,控制器能夠重用,且不能包含太多業務邏輯,一個系統一般也不能設計一個統一的控制器。控制器模式與MVC模式相對應,MVC是一種比設計模式更加高級的架構模式。
(1) 問題:如何處理基於類型的不一樣選擇?如何建立可嵌入的軟件組件?
(2) 解決方案:當相關選擇或行爲隨類型(類)變化而變化時,用多態操做爲行爲變化的類型分配職責。
(3) 分析:由條件變化引起同一類型的不一樣行爲是程序的一個基本主題。若是用if-else或switch-case等條件語句來設計程序,當系統發生變化時必須修改程序的業務邏輯,這將致使很難方便地擴展有新變化的程序。另外對於服務器/客戶端結構中的可視化組件,有時候須要在不影響客戶端的前提下,將服務器的一個組件替換成另外一個組件。此時可使用多態來實現,將不一樣的行爲指定給不一樣的子類,多態是設計系統如何處理類似變化的基本方法,基於多態分配職責的設計能夠方便地處理新的變化。在使用多態模式進行設計時,若是須要對父類的行爲進行修改,能夠經過其子類來實現,不一樣子類能夠提供不一樣的實現方式,將具體的職責分配給指定的子類。新的子類增長到系統中也不會對其餘類有任何影響,多態是面向對象的三大基本特性之一(另外兩個分別是封裝和繼承),經過引入多態,子類對象能夠覆蓋父類對象的行爲,更好地適應變化,使變化點可以「經得起將來驗證」。多態模式在多個GoF設計模式中都有所體現,如適配器模式、命令模式、組合模式、觀察者模式、策略模式等等。
例如:咱們想設計一個繪圖程序,要支持能夠畫不一樣類型的圖形,咱們定義一個抽象類Shape,矩形(Rectangle)、圓形(Round)分別繼承這個抽象類,並重寫(override)Shape類裏的Draw()方法,這樣咱們就可使用一樣的接口(Shape抽象類)繪製出不一樣的圖形,以下圖:
這樣的設計更符合高內聚和低耦合原則,雖而後來咱們又增長了一個菱形(Diamond)類,對整個系統結構也沒有任何影響,只要增長一個繼承Shape的類就好了。
(1) 問題:當不想破壞高內聚和低耦合的設計原則時,誰來負責處理這種狀況?
(2) 解決方案:將一組高內聚的職責分配給一個虛構的或處理方便的「行爲」類,它並非問題域中的概念,而是虛構的事務,以達到支持高內聚、低耦合和重用的目的。
(3) 分析:純虛構模式用於解決高內聚和低耦合之間的矛盾,它要求將一部分類的職責轉移到純虛構類中,在理想狀況下,分配給這種虛構類的職責是爲了達到高內聚和低耦合的目的。在實際操做過程當中,純虛構有不少種實現方式,例如將數據庫操做的方法從數據庫實體類中剝離出來,造成專門的數據訪問類,經過對類的分解來實現類的重用,新增長的數據訪問類對應於數據持久化存儲,它不是問題域中的概念,而是軟件開發者爲了處理方便而產生的虛構概念。純虛構能夠消除因爲信息專家模式帶來的低內聚和高耦合的壞設計,獲得一個具備更好重用性的設計。在系統中引入抽象類或接口來提升系統的擴展性也能夠認爲是純虛構模式的一種應用。純虛構模式一般基於相關功能的劃分,是一種以功能爲中心的對象或行爲對象。在不少設計模式中都體現了純虛構模式,例如適配器模式、策略模式等等。
例如:上面多態模式的例子,若是咱們的繪圖程序須要支持不一樣的系統,那麼由於不一樣系統的API結構不一樣,繪圖功能也須要不一樣的實現方式,那麼該如何設計更合適呢?以下圖:
這裏咱們能夠看到,由於增長了純虛構類AbstractShape,不管是哪一個系統均可以經過AbstractShape類來繪製圖形,咱們即沒有下降原來的內聚性,也沒有增長過多的耦合,可謂魚肉和熊掌兼得!
(1) 問題:如何分配職責以免兩個(或多個)事物之間的直接耦合?如何解耦對象以下降耦合度並提升系統的重用性?
(2) 解決方案:分配職責給中間對象以協調組件或服務之間的操做,使得它們不直接耦合。中間對象就是在其餘組件之間創建的中介。
(3) 分析:要避免對象之間的直接耦合,最經常使用的作法是在對象之間引入一箇中間對象或中介對象,經過中介對象來間接相連。中介模式對應於面向對象設計原則中的迪米特法則,在外觀模式、代理模式、中介者模式等設計模式中都體現了中介模式。
「中介」顧名思義,就是這個事不能直接來辦,須要繞個彎才行。繞個彎的好處就是,原本直接會鏈接在一塊兒的對象彼此隔離開了,一個的變更不會影響另外一個。就像前面的低耦合模式裏說的同樣,「兩個不一樣模塊的內部類之間不能直接鏈接」,可是咱們能夠經過中間類來間接鏈接兩個不一樣的模塊,這樣對於這兩個模塊來講,他們之間仍然是沒有耦合/依賴關係的。
(1) 問題:如何分配職責給對象、子系統和系統,使得這些元素中的變化或不穩定的點不會對其餘元素產生不利影響?
(2) 解決方案:找出預計有變化或不穩定的元素,爲其建立穩定的「接口」而分配職責。
(3) 分析:受保護變化模式簡稱PV,它是大多數編程和設計的基礎,是模式的基本動機之一,它使系統可以適應和隔離變化。它與面向對象設計原則中的開閉原則相對應,即在不修改原有元素(類、模塊、子系統或系統)的前提下擴展元素的功能。開閉原則又可稱爲「可變性封裝原則(Principle of Encapsulation of Variation, EVP)」,要求找到系統的可變因素並將其封裝起來。如將抽象層的不一樣實現封裝到不一樣的具體類中,並且EVP要求儘可能不要將一種可變性和另外一種可變性混合在一塊兒,這將致使系統中類的個數急劇增加,增長系統的複雜度。在具體實現時,爲了符合受保護變化模式,咱們一般須要對系統進行抽象化設計,定義系統的抽象層,再經過具體類來進行擴展。若是須要擴展系統的行爲,無須對抽象層進行任何改動,只須要增長新的具體類來實現新的業務功能便可,在不修改已有代碼的基礎上擴展系統的功能。大多數設計原則和GoF模式都是受保護變化模式的體現。