文章目錄
- GRASP
-
- Creator
- Information Expert
- Low Coupling
- Controller
- High Cohesion
-
- 反例
- Pure Fabrication
- Indirection
- Polymorphism
- Protected Variations
-
- 不要和陌生人講話
- 預測PV和選擇你的戰鬥
- 初始化和*啟動*用例
- 可見性
- 改進耦合
GRASP
Creator
十分有趣!這反映了一種"直覺",OO軟體開發者常常想讓容器建立被容納的事物,就像Board建立Square一樣
建議
如果以下條件之一為真時(越多越好),将建立類A執行個體的職責配置設定給類B:
- B包含或組成聚集了A
- B記錄A
- B緊密地使用A
- B具有A的初始化資料
上面的說法展現了這樣一種暗示:沒有标準答案,因為可能有好幾個候選答案,隻是選擇了暫時占數量優勢的那個.特别是業務邏輯發生變動後,第一名可能變化,這個時候就要修改建立者麼?
另外結合restful而言,這是否需要将建立A的操作POST到B的URL呢?
上面有些标準幾乎是廢話,而有些不知所雲,例如B記錄A,為什麼A不能記錄自己,B是記錄A的數量資訊麼?另外我一般采用的标準是誰的資訊能決定A是否建立成功難麼就由誰來建立A
另外直接建立A看上去也是一個公平的方案,特别是對于建立A有好幾個候選者而言.
另外需要注意的問題是B建立了A之後對A的操作會影響B麼?還是直接修改B?也就是說A是否是B的一部分
組合聚集部分,容器容納内容,記錄者進行記錄,所有這些都是類圖中類之間極為常見的關系.建立者模式建議,封裝的容器或記錄器類是建立其所容納或記錄的事物的很好的候選者.
這裡其實是認知上的回歸,在我看來,容器的目的(例如合同)就是為了容納内容(例如條款),是先有了内容,再有容器來容納,容器就是為了這個目的而生的.隻是後來在操作順序上發生了改變.
對象的建立常常具有相當的複雜性…在這些情況下,最好的方法是把建立職責委派給稱為具體工廠或抽象工廠的輔助類,而不是使用建立者模式所建議的類
Information Expert
建議
把職責配置設定給具有完成該職責所需資訊的那個類
當有多個局部資訊專家時,将職責賦予具有支配作用的資訊專家,即持有主要資訊的對象.這樣有助于支援低耦合
當存在多個選擇時,考慮每個選擇對耦合和内聚的影響,由此選擇最佳方案
當基于其他準則還是無法明确地選擇時,考慮未來演化方向
上面第二條通俗的來講就是誰持有的資訊多,就用它
上面第三條通俗的來講就是誰的責任少,就分給他
上面第四條也是廢話,不過這觸發了我的靈感,就是可以先把這個職責放一放,等其他職責明确了之後再使用上面方案
有時,可以通過尋找具有初始化資料的類來确定建立者,這些資料将在建立過程中傳遞給被建立者.這實際上就是專家模式的例子
在某些情況下,專家模式建議的方案也許并不合适,通常這是由于耦合和内聚問題所産生的
例如,誰應當負責把Sale存入資料庫呢?的确,大多數要儲存的資訊位于Sale對象中,于是專家會建議将此職責配置設定給Sale類…是以,Sale類不僅僅關注"作為銷售"的純應用邏輯.現在由于存在其他職責而降低了它的内聚
這裡對于資訊可以進行多重了解,一種是按照種類了解,在存儲中多個attribute隻能算為一類,多個行也算一類,然後資料庫的連接配接資訊算一類,這樣進行權重處理後Sale不再是存儲時的專家了.另一個更勉強的看法是把attribute看做多個資訊,但是同樣的多個column(因為列名和attribute可以不一樣)也看做多個資訊,再考慮資料庫連接配接資訊,Sale的資訊不再充足.當然,這兩種觀點都牽強了點
持非是Controller或Create,Information Expert是我們應首先考慮應用的模式
可見性,文中舉了一個例子講到當一個操作需要兩個Expert時把這個操作給到哪個Expert,其依據原則是可見性.具體分析如下
其職責陳述如下:
誰負責獲知支付餘額呢?
為了計算支付餘額,需要知道銷售總額和所收取的支付現金總額.是以,Sale和Payment是解決這個問題的部分資訊專家.
如果主要由Payment負責獲知餘額,那麼他需要具有Sale的可見性,以便向Sale請求銷售總額.由于到目前為止Sale對于Payment來說還不可見,是以會增加整體設計的耦合度–不支援低耦合模式
相反,如果主要由Sale負責獲知餘額,他需要具有Payment的可見性,以便向Payment請求所收取的現金總額.由于Sale是Payment的建立者,Sale已經擁有對Payment的可見性,是以該方法不會增加設計的耦合度,是以這是較好的設計
面對這個問題,我的一點個人看法.如果Payment必須依賴于一個Sale的情況下,何況還必須由Sale來建立的情況下,考慮把Payment作為Sale的内部類,這樣的情況下可見性是反過來的,即被建立者可見建立者.另一個視角是考慮一對多的情況,結論是在一的一方
Low Coupling
建議
配置設定職責以使耦合保持在較低的水準.
上面幾乎是一句廢話
因為低耦合往往能夠減少修改軟體所需時間,工作量和缺陷
在C++,Java和C#這樣的面向對象語言中,從TypeX到TypeY耦合的常見形式包括:
- TypeX具有應用TypeY的執行個體或TypeY自身的屬性
- TypeX對象調用TypeY對象的服務
- TypeX具有以任何形式引用TypeY的執行個體或TypeY自身的辦法.通常包括類型TypeY的參數或局部變量,或者由消息傳回的對象是TypeY的執行個體
- TypeX是TypeY的直接或間接子類
- TypeY是接口,而TypeX是此接口的實作
其中子類可否用子集來了解
低耦合的極端例子是類之間沒有耦合.這個例子違反了對象技術的核心隐喻:系統由互相連接配接的對象構成,對象之間通過消息通信.
高耦合對于穩定和普遍使用的元素而言并不是問題.例如J2EE應用能安全地将自己與java.util耦合,因為Java庫是穩定,普遍使用的
高耦合本身并不是問題所在,問題是與某些方面不穩定的元素之間的高耦合,這些方面包括接口,實作等.
作為設計者…你必須在降低耦合和封裝事物之間進行選擇,應該關注在實際當中極其不穩定或需要改進的地方
優點
- 不收其他構件變化的影響
- 易于單獨了解
- 便于複用
Controller
這個原則是用于回答上面的的???處應該是什麼.
名稱:控制器
問題:在UI層之上首先接收和協調系統操作的對象是什麼?
建議
把職責配置設定給能代表下列選擇之一的
- 代表全部系統,根對象,運作軟體的裝置或主要子系統
- 代表發生系統操作的用例場景
我的一般做法是在???處放置UI層上對應呈現資訊的domain,這樣前後端是一緻的.
控制器又可以分為外觀控制器和用例控制器兩類,前者代理了幾乎所有操作,而後者是各種用例的Handler
在這種模式下,所有對domain的調用都要通過控制器,即使是富用戶端的内部調用,也就是說除了控制器,其他地方都不允許調用領域層.是以複用的機關是控制器,他們代理了domain的各種操作
High Cohesion
建議
職責分離應保持高内聚,以此來評估備選方案
這是另一句廢話
個人思考:高内聚的兩種思考方式:一個操作盡可能影響多的屬性一個屬性盡可能受多的操作的影響,這是依從内部觀察的視角,從外部來看,當調用一個對象時盡可能調用其多的方法,
提高内聚的一種方式是如下圖
這點從應用的類也可以知道,另一方面
var a = createA();
b.add(a)
如果上面的兩行代碼總是同時出現(這是個現象),探究其背後的原因後(這是業務上的訴求)可以改成下面代碼
b.createA()
class B{
public void createA(){
createA();
//...
}
}
關于内聚書中舉了一個很好的例子來說明要避免現實世界中(特别是關系型資料庫)的影響,遺憾的時我很久才參悟到這個道理
假設存在稱為Company的類,他負責了解所有雇員資訊和财務資訊的工作.這兩個領域雖然在邏輯上都與公司概念相關,但是彼此之間并沒有太多關聯.此外,其公共的方法并不多,其代碼總量也不多.
反例
有兩種情況可以違反高内聚的原則:
- 由于開發人員的技能不同(并且短時間無法通過學習來精通)進而導緻每個開發人員維護自己技術域的工作,書中的一個例子是OO程式員和SQL專家各自維護.這是一種向人員妥協的情況
- 另一種是向性能妥協的情況(不過在我看來恰當的低耦合高内聚應該是有利于性能解決的)在調用遠端方法時盡肯能一次調用代替多次調用,文中舉的例子是使用setData來代替setName,setSalary和setHireDate,但我覺得這不是一個好的例子.我倒是覺得在寫操作中比較少遇到(除非頁面上就展現出兩個操作被合并了),倒是查詢時,特别是多個下拉框的查詢也許會遇到
Pure Fabrication
面向對象設計有時會被描述為:實作軟體類,使其表示真實世界問題領域的概念,以降低表示差異;然而,在很多情況下,隻對領域層對象配置設定職責會導緻不良内聚或耦合,或者降低複用潛力.
第一個例子給我的感覺是純虛構都是會産生一些非業務的技術類,但是第二個例子給出了不一樣的答案,這裡完全遵循了高内聚和複用的設計思想.
此類對象的設計可以廣泛地分為兩組
通過representational decomposition所表示的選擇
通過behavioral decomposition所産生的選擇
諸如Sale等軟體類的建立是根據表示解析得來的,這種軟體類涉及或代表領域中的事物.表示解析是對象設計中的常見政策,并支援低表示差異的目标.但是有時,我們需要通過對行為分組或通過算法來配置設定職責,而無需建立任何名稱或目的與現實世界領域概念相關的類.
禁忌那些對象設計初學者和更熟悉以功能組織和分解軟體的人有時會濫用行為解析及純虛構對象.誇張的是,功能正好變成了對象…如果濫用純虛構,會導緻大量行為對象,其職責與執行職責所需的資訊沒有結合起來,這樣會對耦合産生不良影響.其通常征兆是,對象内的大部分資料被傳遞給其他對象用以處理.
Indirection
問題為了避免兩個或多個事物之間直接耦合,應該如何配置設定職責?如何使對象解耦合,以支援低耦合并提高複用性潛力?
解決方案将職責配置設定給中介對象,使其作為其他構件或服務之間的媒介,以避免他們之間的直接耦合.中介實作了其他構件之間的間接性
計算機科學中大多數問題都可以通過增加一層間接性來解決.大多數性能問題都可以通過去除一層間接性來解決.
Polymorphism
Protected Variations
問題如何設計對象,子系統和系統,使其内部的變化或不穩定不會對其他元素産生不良影響?
解決方案識别預計變化或不穩定之處,配置設定職責用以在這些變化之外建立穩定接口
不要和陌生人講話
不要曆經遠距離的對象結構路徑去向遠距離的間接對象發送消息.這種設計對于對象結構中的變化而言極為脆弱.
隻應該給以下對象發送消息
- this
- 方法參數
- this屬性
- 作為this屬性的集合中的元素
- 在方法中建立的對象
并不總是需要對此進行防範,這種防範依賴于對象結構的不穩定性.在标準庫中,對象類之間的結構連接配接是相對穩定的.
預測PV和選擇你的戰鬥
首先,值得定義以下兩個變更點
- 變化點:現有,目前系統或需求中的變化
- 進化點:預測将來可能産生的變化點,但并不存在于現在需求中
有時…也就是說,對進化點的預防性工程成本要高于對簡單設計重做的成本
初始化和啟動用例
大多數系統都具有隐式的或顯式的啟動用例,并且具有與應用啟動相關的初始化系統操作.盡管startUp系統操作是最早要執行的操作,但是在實際設計中要将該操作互動圖的開發推遲到其他所有系統操作之後.這一實踐保證能夠發現所有相關初始化活動所需的資訊,這些活動将用于支援其後的系統操作互動圖.
準則
最後完成初始化的設計
可見性
-
全局可見
B是全局變量
-
屬性可見
B是A的屬性
-
參數可見
B是A方法的參數
-
局部可見
B是A方法的變量
對于後兩種有的時候會把方法可見性轉換為屬性可見性,有的時候反過來,通過調用一個get方法來擷取屬性可見性(一般是List或Map)裡的一個元素,這個時候,元素是局部可見
這裡附帶一張圖展現了從Controller到Domain中id程式設計了對象
改進耦合
當對象A持續需要對象B中的資料時,意味着
- 對象A不應該持有該資料
- 對象B而不是對象A應該具有這一職責(基于專家模式)
因為Player與其所處于的Square具有持續的協作,是以是Player而不是Piece永遠需要知道自己所在的Square位置.
對此設計進行了精化,将知道其所處方格的職責配置設定給Player而不是Piece…實際上,我們甚至可以懷疑Piece對象在領域模型中是否有用.
有時,開發者會針對某些未知的可能性變化進行"未來驗證"的推測,由此而使用接口和多态來設計系統…但是,這裡需要有臨界評價,因為有些時候使用多态設計形成的變化點在實際中不一定發生或從未設計發生過,這種不必要的付出很常見.在投入靈活性的改進前,要現實的對待可變性的真實可能性.