1. 程式人生 > >#原創分享# DDD領域建模之OOP進階第一篇【值物件】

#原創分享# DDD領域建模之OOP進階第一篇【值物件】

      接著上期的內容我們繼續開展談話的內容,作為DDD構建的基石----【OOP】是其最基本的構成,計算機軟體用於無限趨近的還原我們現時社會的某種活動,一切皆可建模(這樣的思想不一定全對,但在一定的歷史時期內,還是具有一定的普世性),不知道OOP是什麼時候開始提倡的,也許是從當年的C++語言開始的,從而開創一個偉大的程式設計時代~~~~,在我們講解DDD內容之前,OOP思想已經被無數人講解與定義,在講解也是老調重彈,沒啥新意,我們在這裡要提出一個粒度更細的概念---值物件,他與【OOP】比較,則是粒度更為精細的物件模型劃分,如果把OOP看作 分子的化,【值物件 】可以看成是原子了(不一定是最恰當的,但是基本可以說明一些問題的實質),【值物件】與【OOP】之間存在者不可分隔的關係,在一定程度上甚至可以互相置換使用,那麼【值物件】具備那些特徵呢?為什麼會存在【值物件】這樣的概念呢?他的存在解決了什麼問題呢等等,接下的來的篇幅逐一為大家解答,這也是我在DDD建模從理論到實踐過程中的一些新的體會........

       儘管我也討厭給名詞下定義,下定義這意味者對定義本身的一種束縛,限制的大家想象他的無限可能或者理解的無限可能,但是在這裡還是要採用教科書模式一樣的定義,大體而言:【值物件】用於度量與描述事物,在實際的操作中,特別是後期使用到的【聚合】的場景中,我們要儘量的使用【值物件】而不是實體物件本身,【值物件】具有如下的大概含有如下一些特定的屬性:

     1、他是描述事物本身的最小單位,這個理解就比較難了,什麼是最下單位呢?我這邊可以舉個例子來,我們在軟體開發中常常定義一些計量單位 時間、長度、貨幣等,如果僅僅有單位  缺少了數字這個標量,那麼他的存在不具備任何業務意義,例如 米、釐米等,所以我們必須用 數字+某個特定的計量單位 合併在一起,才能清晰的表述某一個事實或者業務本身,如 1釐米、1小時等。上述兩個元素缺一不可,在引申一下,這樣的業務模型在建模的同時  內部已經隱含了 單位之間的換算 如 1米=100釐米,  大家按照這個思路 可以大概瞭解【值物件】的一些概念了把,在實際的場合中,構建事物本身的小單位:其每部隱藏了一些條件,這些條件在顯式的告訴你:對於物件而言,只有確定不變的屬性,才能構成值物件,用這些屬性的組合可以精準的描述一個實際的業務模型,就常規而言,例如 如果我們針對 集裝箱 而言,如果用【值物件】的概念進行建模,那麼那些屬性才是箱子 恆古不變的呢?這些資料一旦展現,立刻就可以確定他是這個世界上唯一併且確定的那隻集裝箱呢?  如  箱型,箱號、出場日期、出廠廠家 ,伴隨箱子的誕生,這些已經作為箱子不可分隔的不部分,這些值都應該 用 final 來修飾,標識這些屬性是無法被修改的,除了被銷燬以外,在沒有其他渠道可以修改這些屬性。

  2、【值物件】的不可變性,一旦我們在程式中構建值物件本身,那麼他就成為具體某一個物件的代名詞,程式碼體現則更加明顯的是所有的屬性都是 final 來修飾的,大家都知道 final 型別的屬性,都是不可變引數。在這裡還需要確保的是,【值物件】在被我們初始化後,變沒有任何方法可修改,或者不提供任何修改內部屬性的方法,反射都是不用許的,所以用final修飾的值物件是最為穩妥了,我在程式碼的過程中,一旦簡單 有 final 修飾符修飾的變數,總有一種莫名的安全感。另外 【值物件】任何時候它是一個整體,不要妄動區域性的概念,~~~~~~在實際的編碼中:我常常在多個物件構建的【聚合】中使用,畢竟這個世界是複雜的,任何一個事物的構成都不是單一物體,所以大部分而言,都是N個物件相互協助,形成一個更加完備的業務模型或者其他什麼,這時,每個物件在不同的角度來觀察,會有所側重,這邊給我使用【值物件】提供了優厚的條件,例如 實體模型 訂單。在購物車模式下。與在結賬模式下,都是訂單,但是關注點不同,在購物車模式下 訂單 更加關注商品本身的屬性,而在結賬模式下,訂單更加關注的是支付相關的實體物件。。。。。。 活學活用,在一些特定的場合下,我們可以打破【值物件】表示單一事物的規則,而是把幾個或者N個事物合體為一個更加完整的big【值物件】。。。。更加方便、快捷的對外提供穩定,不變的服務。

   3、【值物件】的可替換性,這個理解比較字面一些,就是該物件是原子性存在,要不是他本身,要不替換一個全新的值物件。

  4、【值物件】的判斷相等性,這一點需要大家在程式設計的時候注意,兩個一樣的值物件 判斷的結果應該是與預期一致的,例如 1米=100釐米 類似這樣的場景,這樣就需要我們在編寫程式的時候 rewrite  hasCode 與 equesl 的時候,需要考慮多種情況的場景,這也是我比較頭大的地方,一開始用DDD變成,rewrite 方法的時候,特別的彆扭,但是需要認真的對待,特對是針對。list set 度量 等 這些不可以通過直接 判斷得出的結論,都需要我們重寫這些物件的相等方法。另外構成【值物件】的其他物件也 應該都是【值物件】。 這裡在 我強烈推薦 guava 的Objects 自帶的一些方法,在屬性判斷是否一致上方便不少,JDK8 之後 也引入了較為簡單的 判等方法,極大的方便了我們編碼~~~ 阿彌陀佛~~

  2、【值物件】的無副作用行為,具體體現為方法都是安全、無副作用。值物件的第一個屬性,標識了值物件屬性的不可修改性。那麼第二個屬性是針對第一屬性的再次延展。在物件內部的屬性 對外暴露的方法都是 readOnly 的。 不可否認,在某一些場景下,【值物件】存在一些值修改的場景,遇到這樣的場景程式設計師如何支援呢?想來用OOP的思想已經有了答案:直接程式碼說明,豈不更加有說服力:

public class ValueObj {

    private final int value; // 度量
    private final String unit; // 單位

    public ValueObj(int value, String unit) {
        this.value = value;
        this.unit = unit;
    }

    // ... 一些讀方法 以及   toString  equals  hashCode

    // 無副作用的方法
    // clone 也是一種解決方案 不拘一格
    public ValueObj changgeMe(int value) {
        return new ValueObj(value, this.unit);
    }
}

     這對【值物件】而言,所有的改變都是在創造新的物件,自己本身並會有任何的變化,貌似這樣的一個平常、普通的屬性, 需要大家自己的體會與琢磨其中的內涵......,當一個複雜的事物構建是,大多數情況下,實體物件都可以採用【值物件】來代替,他固有的不變性為我們引用帶來了方便。

 

      當我們掌握了【值物件】的基本概念之後,是否你也想嘗試在實際的編碼過程中,開始使用【值物件】呢?不要著急,慢慢來~~ 一開始可能對值物件的定義有一定的偏差,但是沒關係的,至少我們在OOP建模時候,有了更加清晰的目標與可遵循的道路。

     回憶當年我開始接觸DDD,開始嘗試基於【值物件】的概念構建程式是,是很蹩腳的,生搬硬套~~~~還不斷的指導下級兄弟,想來多少有些汗顏~~~,隨著時間的推移,【值物件】使用的越來越得心應手了,他的不變性是我在程式設計的過程中可以做更少的職責假設,在【聚合】中更加靈活的裝配而滿足不同場景(界限上下文)的需要,這點 類似Hibernate,但是又有本質的區別。 我們要嘗試採用【值物件】來表示一些標準的的實體型別概念,Java中的列舉型別也算是一種最簡單的【值物件】吧。

    通常而言,我們會為【值物件】構建建構函式,在建構函式中完成初始化,但是在實際操作時,從持久化層還原這些屬性可能有先後順序,沒關係的,我先通過 建構函式初始化第一波之後,在通過 setter 方法構建起他的屬性,需要注意的時這些屬性儘可以初始化一次後,便不再有初始化的機會了,從而保證值物件的不可變性,程式碼如下展示:

// ...... 程式碼片段
void setterXXXXX(XXXX xxxxx){
   if(this.xxxx == null)
        this.xxxx = xxxxx;
}

 上述僅僅是一種程式設計的上技巧,還是那句話~~ 程式時靈活的~~~ 用到熟練時,一切都簡單了。上述的方法 展示了一種自我委派性,也是我比較推薦的一種【值物件】初始化的一種方法。

     還有一點需要強調的時【值物件】在隨宿主物件clone時,可採用 淺複製 或者 深複製模式,這些都需要通過建構函式完成自身的複製,我一般不會考慮太多複製的模式,理由很簡單:他是不可變的,N個不同的物件完全可以共享一個例項,除非。你要考慮替換值物件本身的。