對象的職責
對象和數據的主要差別就是對象有行為,行為可以看成責任職責(responsibilities以下簡稱職責)的一種,理解職責是實現好的OO設計的關鍵。“Understanding responsibilities is key to good object-oriented design”—Martin Fowler 。
《對象設計:角色、責任和協作"(Object Design: Roles, Responsibilities, and Collaborations)》一書對對象的職責進行了完整闡述。
對象過去一直被看成是被操作的數據,這也是失血貧血模式的來由,這還是一種將對象看成數據結構或集合的另外一種表現,對象是有自主行為的,對象是一種類似機器人的概念。
職責的革命影響力
在DDD領域驅動設計中,通常很多人會誤將現實中實體完全映射到軟件中領域實體,這是一種謬誤,現實中實體不是一對一反映到軟件中,這其中包括一個抽象過程。我們必須明白:軟件領域中一個實體對象通常能夠扮演多個現實中的實體角色。
由于軟件領域中對象根據不同場景可以扮演不同角色,對象的方法可以看成這些角色不同職責的表現,打個比喻:你在家是一個父親,在單位是一個領導,如果我們建立兩個實體父親和領導就不合適了,沒有經過抽象分析,其實只有一個領域模型實體,就是“你”,只不過在不同場景,你扮演不同角色,角色不同反映在角色的權利或職責行為不同,父親的職責責任與領導的職責責任是不一樣的。
同一個領域對象通過不同方法扮演不同角色就帶來實現上的一個問題,這就衍生出一種DCI架構。我們建模時,不可能將其扮演的所有角色行為都塞入實體對象中,而是應該根據不同運行場景來動態分配職責。所以,傳統OO語言如Java, C#等有些難點,當然可以通過AOP的MIXIN方式變相達到,但是不是很優雅。Qi4J所謂面向組合概念實際就是這樣,而Jdonframework則從EDA事件驅動方面婉轉來通過事件消息來驅動不同職責響應,最新面向函數式語言如Scala這方面就優雅直接了。
職責概念給OO世界帶來巨大革命性變化,使得我們分析需求必須從職責驅動重新看待需求了。DDD一書中分析貨運這個案例時,也未能從職責來審視。比如它為運輸歷史專門建立一個記錄對象,如果從職責概念看,運輸對象應該知道它自己過去的歷史,這是它的職責。所以,運輸歷史獲得應該是運輸實體對象的一個方法,而不應該為其單獨建立對象。
DDD一書很多方面還是存在數據提取的影子,這是它的歷史局限造成的,為了擺脫失血模式純粹數據對象的影子,對象的職責上升到前所未有的高度。
如何發現職責
如果改變傳統將對象看成是靜止的實體概念,將實體對象看成是活的,你就很容易發現其行為職責,現實世界的對象可能是做事情或代表一些信息或東西,但是現實對象不做決定。軟件中對象是活的,根據分配給他們的職責能做決定,類似智能機器人。
職責來自于你的軟件是如何工作,來自于軟件的HOW。尋找和分配職責需要靈感,是一個創造性活動,是一個充滿探索冒險發現新奇的樂趣活動,從下面幾個方面尋找需求中職責:
1. 來自用例分析中序列圖消息發送。
2. 構造invention、 約束表達、策略、算法、規格Specification和描述Description都可以成為職責。
3. 系統要做的事情或要管理的信息
4. 將實體對象看成一個演員(擬人化),扮演一個角色應該知道哪些事情knows something、會做那些事情do sth.,能夠控制或決定什么事情。
簡明扼要,判斷職責的主要就是:它是否知道know這些東西,它是否會做這些東西,或做一些判斷決定等。
職責分配
將職責分配給對象,使得對象有形有態。
按照高內聚原則分配。
使用“如果沒有這個職責,會怎樣”。
如果發現職責太廣泛,不能分配到單個對象中,那么就切分職責,由這些小職責組合成更大職責。
所謂高內聚原則:和DDD中的高聚合概念比較類似,關注類內部;一個類是否充分實現其職責目標?類中方法是否都是為實現這個職責服務的?高聚合代表魯棒性 重用性和可理解性。
總結
一旦領域對象具有豐富的行為,變成富模型,或充血模型,它實際上就是一個Actor,Actor之間可以通過協作消息進行聯系,而Scala的不同于多線程機制的Actor模型底層實現又更好地支撐了這樣的富模型,這也是為什么Scala開始非常流行的原因之一。
使用職責來分析需求,建立豐富對象,類似文學中的擬人化手法,將設計想象要賦予現實中靜止不會說話的客觀事物。當然,不能過,合適即可,目前最大問題是不夠。