值對(duì)像和實(shí)體是兩種領(lǐng)域?qū)ο?,也是最重要的領(lǐng)域模型,跟實(shí)體相比,值對(duì)像沒有唯一標(biāo)識(shí),由對(duì)象的屬性值一起來標(biāo)定一個(gè)對(duì)象實(shí)例。書中說“我們應(yīng)該盡量使用值對(duì)像來建模而不是實(shí)體對(duì)象”,為啥作者會(huì)推崇值對(duì)像呢?
值類型的特點(diǎn)概述:值類型用于度量和描述事物,我們可以非常容易地對(duì)值對(duì)像進(jìn)行創(chuàng)建、測(cè)試、使用和維護(hù)。
值對(duì)像的特征
度量和描述:值對(duì)像不應(yīng)該成為領(lǐng)域中的一件東西,而是用來度量或描述領(lǐng)域中的某件東西的一個(gè)概念。這可以作為值對(duì)象的一個(gè)定義。
不變性:一個(gè)值對(duì)象在創(chuàng)建之后便不能改變。這是一個(gè)非常“技術(shù)性”的建議。那如果領(lǐng)域中的這件東西的屬性變化了,怎么表示呢?創(chuàng)建一個(gè)新的值對(duì)象進(jìn)行替換,比如修改一個(gè)光標(biāo)的位置,應(yīng)該是cursor.position = new positiont(x, y), 而不是cursor.position.x=nx;cursor.position.y=ny;這里的position就是一個(gè)值對(duì)像。也引申的,一個(gè)值對(duì)像里頭,是不建議引用一個(gè)實(shí)體對(duì)象的。
概念整體:一個(gè)值對(duì)象可以只處理單個(gè)屬性,也可以處理一組相關(guān)聯(lián)的屬性,但這些相關(guān)聯(lián)的屬性中,每一個(gè)屬性都是整體屬性所不可或缺的組成部分。比如上面舉例的position的x、y坐標(biāo)是必須在一起才能表達(dá)清楚概念的,比如書中舉的例子貨幣度量,必須有amount數(shù)量和currency貨幣單位?!懊總€(gè)值對(duì)像都是一個(gè)內(nèi)聚的概念整體,它表達(dá)了通用語言中的一個(gè)概念”
可替換性:“在你的模型中,如果一個(gè)實(shí)體所引用的值對(duì)像能夠正確地表達(dá)其當(dāng)前的狀態(tài),那么這種引用關(guān)系可以一直維持下去。否則,我們需要將整個(gè)值對(duì)象替換成一個(gè)新的值對(duì)象實(shí)例”,也如上面不變性說的,如果實(shí)體對(duì)象的屬性變了,那就創(chuàng)建一個(gè)新的屬性表示的值對(duì)像,來替換原來的屬性。
值對(duì)像相等性:如果兩個(gè)值對(duì)象的類型和屬性都相等,那么這兩個(gè)對(duì)象也是相等的。可以跟實(shí)體的相等性比較,實(shí)體的相等性判斷是以實(shí)體標(biāo)識(shí)是否相等為依據(jù)的(標(biāo)識(shí)相同,屬性不同,那是一個(gè)實(shí)體的不同狀態(tài))。所以“如果兩個(gè)或多個(gè)值對(duì)象實(shí)例是相等的,我們便可以用其中一個(gè)實(shí)例來替換另一個(gè)實(shí)例”。比如兩個(gè)position的x、y都相同,那肯定是標(biāo)識(shí)平面上的同一個(gè)位置,那我們使用那個(gè)position實(shí)例都是可以的。
無副作用行為:無副作用函數(shù)表示對(duì)某個(gè)對(duì)象的操作,它只用于產(chǎn)生輸出,而不會(huì)修改對(duì)象狀態(tài)。比如Java中大家最熟悉的String類,就有大量無副作用函數(shù),比如String s1 = "test1", String s2 = s1.substring(1); 這時(shí)候產(chǎn)出新的字符串s2,s1是不會(huì)有任何修改的,即substring是無副作用的。回到值對(duì)像上,值對(duì)像有不變性,那么值對(duì)像的成員函數(shù),應(yīng)該設(shè)計(jì)成無副作用函數(shù)。這種強(qiáng)大的無副作用特性,大家可以借用String體會(huì)體會(huì),不用任何特殊處理就是線程安全的。關(guān)于不變對(duì)象的好處,大家可以隨便google一下就有很多。
值對(duì)像兩個(gè)重要使用場(chǎng)景
正因?yàn)橹祵?duì)像的這些特征和好處,引申出兩大重要的使用場(chǎng)景:
上下文集成:在上下文集成時(shí),模型概念從上游上下文流入下游上下文時(shí),盡量使用值對(duì)像來表示這些概念。這樣的好處是可以達(dá)到最小化集成,即可以最小化下游模型中用于管理職責(zé)的屬性數(shù)目。使用不變的值對(duì)像使得我們做更少的職責(zé)假設(shè)。怎么理解呢,如果傳遞是實(shí)體呢?實(shí)體只由唯一標(biāo)識(shí)決定,有狀態(tài)變化的,那么上下游是不是要同步?也代表著上下游上下文有一個(gè)無法解耦的唯一標(biāo)識(shí)連著的。再回想架構(gòu)中的REST或HTTP協(xié)議,請(qǐng)求的一次響應(yīng)代表著資源在當(dāng)時(shí)的狀態(tài)的一種描述,下游(客戶端)拿到后就是不變的,就是一種值對(duì)像。
用值對(duì)像表示標(biāo)準(zhǔn)類型:標(biāo)準(zhǔn)類型,比如貨幣類型的CAD、CNY、USD,比如時(shí)間單位的時(shí)、分、秒,這些比較好理解,肯定是適合用的不變的值對(duì)像來表示的,一般就是直接用枚舉類型。除了枚舉類型,一般也使用狀態(tài)模式來實(shí)現(xiàn)標(biāo)準(zhǔn)類型,這也是值對(duì)像的范疇內(nèi)。
測(cè)試實(shí)現(xiàn)與持久化
在測(cè)試和實(shí)現(xiàn)中,值對(duì)像一般只暴露完善的構(gòu)造函數(shù)和無副作用的函數(shù),所以注冊(cè)值對(duì)像的不變性。
在持久化值對(duì)像一節(jié)中,現(xiàn)在比較常見的做法是兩種:多個(gè)值對(duì)象序列化到單個(gè)列中存在,一般借助KV或文檔型的Nosql存儲(chǔ);使用數(shù)據(jù)庫實(shí)體保存多個(gè)值對(duì)象,即使用一個(gè)委派標(biāo)識(shí)來保存值對(duì)象,并與引用它們的實(shí)體關(guān)聯(lián)。