GRASP設計模式及OO設計原則淺談

說到設計模式,更爲人所知的當然是GoF(Gang of Four)的23種設計模式。

與GoF的23種設計模式不同的是,GRASP設計模式描述的是在OO設計中爲互相協作的類分配職責的原則或者建議,而GoF的設計模式則是在更高的層次上描述一個OO系統或者其局部系統的行爲以及結構上的抽象。

 

GRASP設計模式的全稱是General Responsibility Assignment Software Patterns,即通用職責分配軟件模式。它定義了9個基本的OO設計原則或基本的設計構件。這9個設計模式分別是:創建者(Creator)、信息專家(Information Expert)、低耦合(Low Coupling)、控制器(Controller)、高內聚(High Cohesion)、多態性(Polymorphism)、純虛構(Pure Fabrication)、間接性(Indirection)、防止變異(Protected Variations)。

 

一、創建者(Creator

實際應用中,符合下列任一條件的時候,都應該由類 A 來創建類 B,這時 A 是 B 的創建者:
a、A 是 B 的聚合
b、A 是 B 的容器
c、A 持有初始化 B 的信息(數據)
d、A 記錄 B 的實例
e、A 頻繁使用 B

如果一個類創建了另外一個類,那麼這兩個類之間就有了耦合,也可以說產生了依賴關係。依賴或耦合本身是沒有錯誤的,但是他們帶來的問題就是在以後的維護中產生連鎖反應,而必要的耦合是逃不掉的,我們能做的就是正確的創建耦合關係,不要隨便建立類之間的依賴關係,那麼該如何去做呢?就是要遵守創建者模式規定的基本原則,凡是不符合以上條件的,都不能隨便用 A 創建 B。

例如:因爲訂單(Order)是商品(SKU)的容器,所以應該由訂單來創建商品。如下圖:
這裏寫圖片描述

這裏因爲訂單是商品的容器,也只有訂單持有初始化商品的信息,所以這個耦合關係是正確的且沒有辦法避免的,所以由訂單來創建商品。

使用創建者模式的好處是不會增加系統的耦合度,因爲根據創建者模式的建議,類的實例的創建者已經與這個類存在着某種形式的耦合。因此該模式支持低耦合的設計,能產生具有較低的維護依賴性與較高的複用性的系統

在一個設計靈活的OO系統中,對象的創建方式往往非常複雜。比如,有些系統需要爲了更好的性能而集中創建或者使用回收的實例(線程池、連接池、對象池等);有些系統需要根據某些條件來創建一族類的實例;甚至有些框架系統在框架的編寫過程中根本不知道需要實例化哪一個類,等等。在這些情況下,最好的方法是將創建類的實例的職責委派給抽象工廠(Abstract Factory)、具體工廠(ConcreteFactory)、創建器(Builder)等等輔助類,而不是創建者模式所建議的類。

 

二、信息專家(InformationExpert

信息專家模式是面向設計的最基本原則,是我們平時使用最多,應該跟我們的思想融爲一體的原則。也就是說,我們設計對象(類)的時候,如果某個類擁有完成某個職責所需要的所有信息,那麼這個職責就應該分配給這個類來實現。這時,這個類就是相對於這個職責的信息專家。
例如: 常見的網上商店的購物車(ShopCar),需要讓每種商品(SKU)只在購物車內出現一次,購買相同商品,只需要更新商品的數量即可。如下圖:
這裏寫圖片描述

針對這個問題需要權衡的是,比較商品是否相同的方法需要放到哪個類裏來實現呢?分析業務得知需要根據商品的編號(SKUID)來唯一區分商品,而商品編號是唯一存在於商品類的,所以根據信息專家模式,應該把比較商品是否相同的方法放在商品類裏。

在信息專家模式給出的建議中,由於對象可以使用自身的信息來完成它的職責,因此信息的封裝性得以維護,從而支持了低耦合;同時,由於類的職責都根據自身所擁有的信息來分配,因而該模式也支持高內聚的設計。

當然,在某些特殊的情況下,信息專家模式也許並不適用,這通常是由於內聚與耦合的問題所產生的。比如許多後臺系統都有把模型(Model)類的數據存入數據庫的功能。這一職責的履行所必需的信息顯然是存在於各個模型類中,按照信息專家模式給出的建議,應該讓這些模型類來完成把自身的數據保存到數據庫中的功能。但是,這樣的設計會導致內聚與耦合方面的問題。首先,在這樣的設計中,所有的模型類都必須包含與數據庫處理相關的邏輯,如與JDBC相關的處理邏輯。這使得模型類由於其他職責的存在而降低了它的內聚。其次,這樣的設計也爲所有的模型類都引入了與JDBC的耦合關係,使得系統的耦合度上升。甚至,這種設計也會導致大量重複、冗餘的數據庫邏輯存在於整個系統中的各個角落,這也違反了設計要分離主要的系統關注的基本架構原則。因此,在這種情況下,信息專家模式需要我們結合整個系統的耦合和內聚做出另外的考慮。

 

三、低耦合(LowCoupling

低耦合模式的意思就是要我們儘可能地減少類之間的連接。

其作用非常重要:

a、低耦合降低了因一個類的變化而影響其他類的範圍。

b、低耦合使用類更容易理解,因爲類會變得簡單,更內聚。

下面這些情況會造成類 A、B 之間的耦合:

a、A 是 B 的屬性
b、A 調用 B 的實例的方法
c、A 的方法中引用的 B,例如 B 是 A 方法的返回值或參數。
d、A 是 B 的子類,或者 A 實現 B

關於低耦合,還有下面一些基本原則:

a、Don’t Talk to Strangers 原則

意思就是說,不需要通信的兩個對象之間,不要進行無謂的連接,連接了就有可能產生問題,不連接就一了百了了。

b、如果 A 已經和 B 有連接,如果分配 A 的職責給 B 不合適的話(違反信息專家模式),那麼就把 B 的職責分配給 A。

c、兩個不同模塊的內部類之間不能連接,否則比招報應!

例如:Creator 模式的例子裏,實際業務中需要另一個出貨人來清點訂單(Order)上的商品(SKU),並計算出商品的總價,但是由於訂單和商品之間的耦合已經存在了,那麼把這個職責分配給訂單更合適,這樣可以降低耦合,以便降低系統的複雜性。如下圖:
這裏寫圖片描述

這裏我們在訂單類裏增加了一個 TotalPrice() 方法來執行計算總價的職責,沒有增加不必要的耦合。

當然,沒有絕對的度量標準來衡量耦合程度的高低。使用低耦合模式的目的是爲了創建一個可靈活伸縮的、可維護的、可擴展的系統。在這個目的之下,低耦合不能脫離信息專家和高內聚等其他模式孤立地考慮,而是應該同時權衡耦合與內聚。高耦合本身也並不是問題之所在,問題是與某些方面不穩定的元素之間的高耦合,這種高耦合會嚴重影響系統將來的維護性和擴展性。而比如所有的Java系統都能安全地將自己去Java庫(java.lang,java.util等)進行耦合,因爲Java庫是穩定的,與Java庫的耦合不會給系統的靈活性、維護性、擴展性帶來什麼問題。

 

四、控制器(Controller

用來接受和處理系統事件的職責,一般應該分配給一個能夠代表整個系統的類,這樣的類通常被命名爲“XX處理器”、“XX協調器”或“XX會話”。

關於控制器類,有如下原則:

a、系統事件的接收與處理通常由一個高級類來代替。

b、一個子系統會有很多控制類,分別處理不同的事務。

控制器設計中的常見缺陷是分配的職責過多。這時,控制器會具有不好的低內聚。因而存在這樣一條準則:正常情況下,控制器應當把需要完成的工作委派給其他的對象。控制器只是協調或控制這些活動,本身並不完成大量工作。

 

五、高內聚(HighCohesion

高內聚的意思是給類儘量分配內聚的職責,也可以說成是功能性內聚的職責。即功能性緊密相關的職責應該放在一個類裏,並共同完成有限的功能,那麼就是高內聚合。這樣更有利於類的理解和重用,也便於類的維護。

高內聚也可以說是一種隔離,就像人體由很多獨立的細胞組成,大廈由很多磚頭、鋼筋、混凝土組成,每一個部分(類)都有自己獨立的職責和特性,每一個部分內部發生了問題,也不會影響其他部分,因爲高內聚的對象之間是隔離開的。

例如:一個訂單數據存取類(OrderDAO),訂單即可以保存爲 Excel 模式,也可以保存到數據庫中;那麼,不同的職責最好由不同的類來實現,這樣纔是高內聚的設計,如下圖:
這裏寫圖片描述

這裏我們把兩種不同的數據存儲功能分別放在了兩個類裏來實現,這樣如果未來保存到 Excel 的功能發生錯誤,那麼就去檢查 OrderDAOExcel 類就可以了,這樣也使系統更模塊化,方便劃分任務,比如這兩個類就可以分配到不同的人同時進行開發,這樣也提高了團隊協作和開發進度。

在少數情況下,較低的內聚也是被接受的。比如,爲了方便專門的數據庫邏輯人員統一管理SQL語句,系統中往往可以將與SQL語句相關的操作都放在一個獨立的全能類中;另外,出於性能的考慮,在RPC中使用一個粗粒度的RPC服務器類,既可以減少服務器上對象的數目,可以減少網絡請求和連接的數目,從而提高系統的性能。這些,都需要從系統的全局出發,結合多種設計的原則進行權衡考慮。

 

六、多態性(Polymorphism

這裏的多態跟 OO 三大基本特徵之一的“多態”是一個意思。

例如:我們想設計一個繪畫程序,要支持可以畫不同類型的圖形,我們定義一個抽象類 Shape,矩形(Rectangle)、圓形(Round)分別繼承這個抽象類,並重寫(override)Shape 類裏的Draw() 方法,這樣我們就可以使用同樣的接口(Shape抽象類)繪製出不同的圖形,如下圖:
這裏寫圖片描述

這樣的設計更符合高內聚和低耦合原則,雖然後來我們又增加了一個菱形(Diamond)類,對整個系統結構也沒有任何影響,只要增加一個繼承 Shape 類就行了。

多態性模式是OO設計的一個基本原則。

 

七、純虛構(PureFabrication

這裏的純虛構跟我們常說說的純虛構函數意思相近。高內聚低耦合,是系統設計的終極目標,但是內聚和耦合永遠都是矛盾對立的。高內聚以爲這拆分出更多數量的類,但是對象之間需要協作來完成任務,這又造成了高耦合,反過來依然。該如何解決這個矛盾呢?這個時候就需要純虛構模式,由一個純虛構的類來協調內聚和耦合,可以在一定程度上解決上述問題。

例如:上面多態模式的例子,如果我們的繪圖程序需要支持不同的系統,那麼因爲不同系統的API結構不同,繪圖功能也需要不同的實現方式,那麼該如何設計更合適呢?如下圖:
這裏寫圖片描述

這裏我們可以看到,因爲增加了純虛構類AbstractShape,不論是哪個系統都可以通過AbstractShape 類來繪製圖形,我們即沒有降低原來的內聚性,也沒有增加過多的耦合。

 

八、間接性(Indirection

間接性模式關注這樣一個問題:爲了避免兩個或多個事務之間直接耦合,應該如何分配職責?如何使對象解耦合,以支持低耦合並提高複用性潛力?

間接性模式對此的回答是:將職責分配給中介對象,使其作爲其他構件或服務之間的媒介,以避免它們之間的直接耦合。中介則實現了其他構件之間的間接性。

間接性模式的思想比較簡單,即通過一箇中介就能消除許多的耦合。在GoF的23種設計模式中,有許多模式都利用到了間接性的思想。比如橋接模式中,設計將抽象部分與其實現部分相分離,利用的就是在客戶與實現之間增加了一個抽象層次。外觀模式則是在整個子系統與客戶之間增加了一個便於用戶使用的外觀類作爲中介。而中介者模式中的中介者則更是典型的例子。

 

九、防止變異(ProtectedVariations

防止變異模式關注這樣一個問題:如何設計對象、子系統和系統,使其內部的變化或不穩定性不會對其他元素產生不良影響?

防止變異模式的回答是:識別預計變化或不穩定之處,分配職責用以在這些變化之外創建穩定的接口。

防止變異(PV)是非常重要和基本的軟件設計原則,幾乎所有的軟件或架構設計技巧都是防止變異的特例。PV是一個根本原則,它促成了大部分編程和設計的模式和機制,用來提供靈活性和防止變化。在軟件設計中,除了數據封裝、接口、多態、間接性等機制是PV的核心機制之外,沒有一種固定的或者是通用的辦法能夠防止一切變化的產生。因此PV的實現依賴的是一系列的OO設計方面的經驗性原則,用以產生一個設計良好的高內聚、低耦合的系統,從而支持PV。

 

要真正發揮OO的強大的作用,關鍵是要深刻理解以上的GRASP模式和設計原則,在此基礎上去再深入理解設計模式,並在實踐中不斷磨練。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章