什麼是GRASP模式?
GRASP模式(一般責任分配軟體模式)描述了物件設計和責任分配的基本原則和模式。 確定需求並建立領域模型後,如何將方法新增到Class類中,並定義物件之間的訊息傳遞以滿足要求。
GRASP模式是一種學習輔助工具,可幫助人們理解基本物件設計,並以有條理,合理,可解釋的方式應用設計推理。這種理解和使用設計原則的方法基於分配責任的模式。
責任和方法
UML將責任定義為“分類器的合同或義務”。責任與物件在其行為方面的義務有關。基本上,這些職責分為以下兩種:
- knowing知道
- doing做
物件的doing職責包括:
- 自己做某事,比如建立一個物件或進行計算
- 在其他物件中啟動操作
- 控制和協調其他物件的活動
物件的knowing職責包括:
- 瞭解私有封裝資料
- 瞭解相關物件
- 知道它可以匯出或計算的東西
在物件設計期間將責任分配給物件類。例如,我可以宣告“銷售是負責建立SalesLineltems”(doing),或者“銷售是負責瞭解其總數”(knowing)。與“knowing”相關的相關責任通常可以從領域模型的屬性和關聯中推斷出來。
POS(銷售點)應用程式用於解釋所有 GRASP 模式 。
讓我們 簡要理解POS (銷售點)應用程式,然後將 GRASP 模式 應用於POS應用程式。
銷售點(POS)應用程式
讓我們考慮一下銷售點(POS)應用程式:POS應用程式的簡要概述。
- 申請註冊銷售的商店,餐館等。
- 每次銷售都是一種或多種產品型別的一個或多個專案,並在某個特定日期發生。
- 產品具有包括描述,單一價格和識別符號的規範。
- 該應用程式還記錄與銷售相關的付款(例如,現金)。
- 付款金額等於或大於銷售總額。
如何應用GRASP模式(一般責任分配軟體模式)
我們來討論五種GRASP模式。每個 GRASP模式 都有一個單獨的帖子
資訊專家GRASP模式
將責任分配給物件的一般原則是什麼?設計模型可以定義數百或數千個軟體類,並且應用程式可能需要履行數百或數千個職責。在物件設計期間,當定義物件之間的互動時,我們會對軟體類的職責分配做出選擇。如果做得好,系統往往更容易理解,維護和擴充套件,並且有更多機會在未來的應用程式中重用元件。
將責任分配給資訊專家 - 具有履行職責所必需資訊的類。(banq注:類似DDD聚合)
在上面POS應用程式中,某些類需要知道銷售的總計總和。通過資訊專家類,我們應該尋找具有確定總數所需資訊的那類物件。一個 銷售Sale誒 是適合這一責任的一類物件, 它是這項工作的資訊專家。
確定訂單項總計需要哪些資訊?
需要 SalesLineltem.quantity 和 ProductSpecification.price 。
SalesLineltem 知道其數量及其相關 ProductSpecification ;
因此,通過專家, SalesLineltem 應該確定總和; 它是資訊專家。
就互動圖而言,這意味著 Sale 需要向每個 SalesLineItem 傳送get-Subtotal訊息並對結果求和; 這個設計如圖所示:為了履行知道和回答總計的責任,Sales- Lineltem 需要知道產品價格。該 ProductSpecification 是在回答資訊專家 的價格; 因此, 必須向其傳送一條訊息,詢問其價格。
總之,為了履行詢問和回答銷售總額的責任,三個責任被分配給三個設計類別的物件如下
Sale: knows sale total 瞭解銷售總額
SalesLineltem: knows line item subtotal 只瞭解每個條目的小計
ProductSpecification: knows product price 瞭解產品價格
示例程式碼:
讓我們在 Sale 領 模型類中編寫一個示例方法:
<b>public</b> <b>class</b> Sale { <font><i>//...</i></font><font> <b>public</b> <b>double</b> getTotal(){ <b>double</b> total = 0; <b>for</b> (SalesLineItem s : salesLineItem) { ProductSpecification prodspec = s.getProductSpecification(); total += s.getQuantity()*prodspec.getPrice(); } <b>return</b> total; } } </font>
這是上面的程式碼就夠了嗎?這是不夠正確的,因為在這裡我們需要 從 salesLineItem進行getSubTotal 。
讓我們在 SaleLineItem 域模型類中建立方法 :
<b>public</b> <b>class</b> SalesLineItem { <font><i>//...</i></font><font> <b>public</b> <b>double</b> getSubTotal(){ <b>return</b> <b>this</b>.getQuantity() productSpecification.getPrice(); } } </font>
觀察 ProductSpecification 域模型提供了getPrice方法給出的價格。
履行責任可能需要跨不同類別的資訊,每個專家都有自己的資料。
優點
- 由於物件使用自己的資訊來完成任務,因此維護了資訊封裝。這通常支援低耦合,這導致更強大和可維護的系統。(低耦合也是GRASP模式,將在下一節中討論)。
- 行為分佈在具有所需資訊的類中,從而鼓勵更易於理解和維護的更具凝聚力的“輕量級”類定義。通常支援高內聚(後面討論的另一種模式)。
GRASP低耦合模式
如何支援低依賴性,低變化影響並增加重用?分配責任以使耦合保持低水平。儘量避免一個類必須knowing瞭解其他許多類。
關於低耦合的要點
- “工件”(類,模組,元件)之間的低依賴關係。
- 模組之間不應該有太多的依賴關係,即使應該通過介面存在依賴關係,也應該是最小的。
- 避免緊密耦合以便在兩個類之間進行協作(如果一個類想要呼叫第二個類的邏輯,那麼第一個類需要第二個類的物件,這意味著第一個類建立第二個類的物件)。
- 努力在互動的物件之間進行鬆散耦合設計。
- 控制反轉(IoC)/依賴注入(DI) - DI物件在建立時由某些第三方(即Java EE CDI,Spring DI ...)給出它們的依賴關係,它們協調系統中的每個物件。不期望物件建立或獲取它們的依賴項 - 依賴項被注入到需要它們的物件中。DI鬆耦合的主要優點。
這是鬆耦合的一個例子: Traveler 類與 Car 或 Bike 實現沒有緊密結合 。相反,通過應用依賴注入機制,實現鬆散耦合實現,以允許與已實現 Vehicle 介面的任何類 。
步驟1: Vehicle 介面允許鬆散耦合實現。
<b>interface</b> Vehicle { <b>public</b> <b>void</b> move(); }
第2步: Car 類實現 Vehicle 介面:
<b>class</b> Car implements Vehicle { @Override <b>public</b> <b>void</b> move() { System.out.println(<font>"Car is moving"</font><font>); } } </font>
第3步: Bike 類實現 Vehicle 介面:
<b>class</b> Bike implements Vehicle { @Override <b>public</b> <b>void</b> move() { System.out.println(<font>"Bike is moving"</font><font>); } } </font>
第4步:現在建立 Traveler 類,其中包含對 Vehicle 介面的引用 :
<b>class</b> Traveler { <b>private</b> Vehicle v; <b>public</b> Vehicle getV() { <b>return</b> v; } <b>public</b> <b>void</b> setV(Vehicle v) { <b>this</b>.v = v; } <b>public</b> <b>void</b> startJourney() { v.move(); } }
步驟5:鬆散耦合示例的 測試 類 - 示例。
<b>public</b> <b>static</b> <b>void</b> main(String args) { Traveler traveler = <b>new</b> Traveler(); traveler.setV(<b>new</b> Car()); <font><i>// Inject Car dependency</i></font><font> traveler.startJourney(); </font><font><i>// start journey by Car</i></font><font> traveler.setV(<b>new</b> Bike()); </font><font><i>// Inject Bike dependency</i></font><font> traveler.startJourney(); </font><font><i>// Start journey by Bike</i></font><font> } </font>
GRASP高凝聚模式
如何讓類Class集中,易懂和易於管理?分配責任,使凝聚力保持高水平。儘量避免一個類做太多或太不同的事情。
凝聚力這個術語用來表示一個類有一個單一的,專注的責任的程度。凝聚力衡量一個類或一個模組的方法是如何有意義和強烈相關的,以及它們在為系統提供明確定義的目的方面的重點。
當一個類在其中包含許多不相關的函式時,它被識別為低內聚類。而我們需要避免的是因為具有不相關功能的大型類妨礙了他們的維護。始終使您的類變得小巧,具有精確的目的和高度相關的功能。
關於高凝聚力的要點
- 程式碼必須在其操作行為中非常具體。
- 責任/方法與lass / module高度相關。
- 凝聚力這個術語用來表示一個類有一個單一的,專注的責任的程度。凝聚力衡量一個類或一個模組的方法是如何有意義和強烈相關的,以及它們在為系統提供明確定義的目的方面的重點。更為集中的一類 是 較高的凝聚力-一件好事。
- 當一個類 在其中包含許多不相關的函式時,它被識別為低內聚類。而我們需要 避免的 是因為具有不相關功能的大型課程妨礙了他們的維護。始終使您的課程變得小巧,具有精確的目的和高度相關的功能。
案例:
在這個例子中, MyReader 類的目的是讀取資源,它只是這樣做。它沒有實現其他無關的東西。因此它具有高度凝聚力。
<b>class</b> HighCohesive { <font><i>// -------------- functions related to read resource</i></font><font> </font><font><i>// read the resource from disk</i></font><font> <b>public</b> String readFromDisk(String fileName) { <b>return</b> </font><font>"reading data of "</font><font> + fileName; } </font><font><i>// read the resource from web</i></font><font> <b>public</b> String readFromWeb(String url) { <b>return</b> </font><font>"reading data of "</font><font> + url; } </font><font><i>// read the resource from network</i></font><font> <b>public</b> String readFromNetwork(String networkAddress) { <b>return</b> </font><font>"reading data of "</font><font> + networkAddress; } } </font>
GRASP建立者模式
誰應該負責某個類的新例項的建立?物件的建立是面向物件系統中最常見的活動之一。因此,指導如何分配建立責任的一般原則是有用的。分配好了,該設計可以支援低耦合,增加清晰度,封裝和可重用性。
如果滿足以下一個或多個條件,則為B類分配建立A類例項的職責:
•B聚合A。
•B包含A。
•B記錄跟蹤A的例項。
•B密切使用A。
•B具有初始化資料,在建立時將傳遞給A(因此B是建立A的專家)。
B是A的建立者。
如果適用上述多個選項,則就傾向於選擇B類為聚合,代表整體,包含了A,A是B的一部分。
在POS應用程式中,誰應該負責建立 SalesLineltem 例項?通過建立者 模式 ,我們應該尋找一個聚合,包含 SalesLineltem 例項等的類。
由於Sale包含(實際上是聚合)許多 SalesLineltem 物件,因此Creator模式表明 Sale 是負責建立 SalesLineltem 例項的良好候選者 。這導致了物件互動的設計:建立SalesLineItem
<b>class</b> Sale { List<SalesLineItem> salesLineItem = <b>new</b> ArrayList<SalesLineItem>(); <font><i>//...</i></font><font> <b>public</b> <b>void</b> addLineItem(ProductSpecification prodSpec,<b>int</b> quantity) { salesLineItem.add(<b>new</b> SalesLineItem(prodSpec, quantity); <b>return</b> salesLineItem; } } </font>
GRASP控制者模式
誰應該負責處理輸入系統事件? 輸入系統事件是由外部角色輸入生成的事件。它們與系統的系統操作相關聯以響應系統事件,就像訊息和方法相關一樣。
例如,當使用文書處理器的作者按下“拼寫檢查”按鈕時,他正在生成指示“執行拼寫檢查”的系統事件。Controller控制器是負責接收或處理系統事件的非使用者介面物件。Controller定義系統操作的方法。誰應該負責處理輸入事件,與UI層以外的物件接收互動?
將接收或處理系統事件訊息的責任分配給表示以下選項之一的類:
- 表示整個系統,裝置或子系統(外觀控制器)。
- 表示系統事件發生的用例場景,通常名為<UseCaseName> Handler,<UseCaseName> Coordinator或<Use-CaseName> Session(用例或會話控制器)。
- 對同一用例場景中的所有系統事件使用相同的控制器類。
- 非正式地,會話是與演員的對話的例項。會話可以是任何長度,但通常根據用例(用例會話)進行組織
(banq注:以上四種模式適合設計DDD聚合根)