領(lǐng)域驅(qū)動設(shè)計(jì)(DDD):實(shí)體

實(shí)體

在對實(shí)體進(jìn)行建模時,我們可能會把重點(diǎn)放在實(shí)體對象或者唯一標(biāo)識屬性的設(shè)計(jì)上,然而對于實(shí)體的本質(zhì)并沒有一個清晰的概念。這將導(dǎo)致你不知道什么是實(shí)體,還要將對象設(shè)計(jì)成實(shí)體的矛盾中。最終可能會為一個模型對象添加一個唯一標(biāo)識(ID)屬性而宣布這個模型對象是個實(shí)體對象。

當(dāng)看到一個計(jì)算機(jī)領(lǐng)域中的概念,你可能會在意它在計(jì)算機(jī)領(lǐng)域中的解釋。但有很多概念并不是計(jì)算機(jī)領(lǐng)域所創(chuàng)造的,它可能來源于其它領(lǐng)域。并且計(jì)算機(jī)領(lǐng)域可能對這個概念并沒有做過多的解釋,只是對概念的應(yīng)用。

認(rèn)識實(shí)體

什么是實(shí)體? 當(dāng)突然被問到這個問題時,你想到了什么?

一個在 E-R 圖中所表示的實(shí)體類型、一個具有唯一標(biāo)識的模型、一個被 @Entity 注解標(biāo)記的模型、一個繼承自 Entity 類型的模型、或者把他們?nèi)诤显谝黄穑阂粋€具有唯一標(biāo)識的并且被 @Entity 注解標(biāo)記(或者繼承自 Entity 類型)的模型。

這些都只是技術(shù)抽象后的實(shí)體模型,而并不是實(shí)體。那什么是實(shí)體呢?

An entity is something that exists separately from other things and has a clear identity of its own.

谷歌翻譯:實(shí)體是與其他事物分開存在的事物,并且具有自己的明確標(biāo)識。

拋開計(jì)算機(jī)領(lǐng)域?qū)δ闼伎忌系氖`,你是如何理解實(shí)體的呢?

形成實(shí)體

zoo

當(dāng)你帶著小朋友去動物園游玩,你指著長頸鹿問小朋友:“這是什么動物呀?”。小朋友會告訴你:“這是長頸鹿?!?。你繼續(xù)問他:“你怎么知道這是長頸鹿的?”,小朋友又告訴你:“因?yàn)樗牟弊雍荛L,身上還有豹紋?!?。你又繼續(xù)問:鴕鳥、河馬、獅子、猴子、大象、...。

小朋友可以通過不同動物的特征來識別和區(qū)分動物。人類的思想是可以表達(dá)這個世界的,這個可以被識別和區(qū)分其他事物的抽象概念被稱作:類型(Type)。

類型的出現(xiàn)使得可以更好地表達(dá)如何區(qū)分事物,這對理解實(shí)體以及實(shí)體的出現(xiàn)又近了一步。

事物之間可以根據(jù)不同類型區(qū)分開來了,比如:猴子(鴕鳥、河馬、獅子)類。但是如何知道一群猴子里的某一只猴子呢?

小朋友來到小猴子的觀賞區(qū),指著一只小猴子說:“猴寶寶”。過了一會那只猴寶寶跳到了一群猴寶寶群里,小朋友此時已經(jīng)無法找到剛剛那只猴寶寶。

如果可以為每一只猴子分配一個唯一的編號,那么小朋友找到這只猴寶寶就變得十分簡單了。
如果小朋友能始終找到這只猴寶寶,那么這個猴寶寶就是一個實(shí)體。

準(zhǔn)備好,我們要開車了!

  • 事物的抽象建模稱之為實(shí)體類型。
  • 對實(shí)體類型的實(shí)例化稱之為實(shí)體。

注意實(shí)例化的對象是有標(biāo)識的:

public class Monkey { // Monkey 是一個實(shí)體類型。
    public static void main(String[] args) {
        Monkey monkey1 = new Monkey(); // monkey1 是一個實(shí)體。
        Monkey monkey2 = new Monkey(); // monkey2 也是一個實(shí)體。
        System.out.println(monkey1 == monkey2); // false
    }
}

但是非常抱歉的是,我們對事物的認(rèn)識遠(yuǎn)遠(yuǎn)沒有那么清晰。

public class MonkeyService { // MonkeyService 是一個類(class)。
    public static void main(String[] args) {
        MonkeyService service1 = new MonkeyService(); // service1 是一個對象(object)。
        MonkeyService service2 = new MonkeyService(); // service2 也是一個對象(object)。
    }
}

應(yīng)用邏輯服務(wù)(MonkeyService)是一個實(shí)體類嗎?它顯然不是,因?yàn)樗皇菍I(yè)務(wù)規(guī)則的封裝,而不是對事物的抽象。他的作用是在執(zhí)行期間訪問各種實(shí)體對象來組合業(yè)務(wù)規(guī)則。

只有對事物的抽象才稱之為實(shí)體。

我們曾經(jīng)花了很長時間來理解透徹 類(class)對象(object) 之間的關(guān)系。

  • MonkeyService 叫做對象類。
  • service1, service2 叫做對象。

我們從來沒有指著 MonkeyService 這個東西叫對象,service1, service2 這個東西叫類。

正是這樣的關(guān)系,我們對事物建模形成的類稱之為實(shí)體類,通過實(shí)例化(new)實(shí)體類而產(chǎn)生的對象實(shí)例稱之為實(shí)體

我喜歡稱它們?yōu)?strong>實(shí)體對象,因?yàn)樗鼈円彩莻€對象。

我還是擔(dān)心,你沒有明白我這么啰嗦的表達(dá)什么是實(shí)體。我決定從另一個角度在探討一次什么是實(shí)體。

再次強(qiáng)調(diào)一下:對事物的建模稱之為實(shí)體類型。

我們反反復(fù)復(fù)的強(qiáng)調(diào),對事物的建模就稱之為對實(shí)體的建模,那什么是事物?什么不是事物呢?

實(shí)體(Entity)可以是一個人、一座城市、一輛汽車、一張彩票或一次交易。

我們會使用各種名詞來代表事物,比如人、城市、汽車、彩票、交易。因此我們會有一種錯覺,對實(shí)體的建模就是對名詞的建模。然而是這樣嗎?

我們在創(chuàng)造一個名詞時,并不是先創(chuàng)造一個名詞然后在指向一種事物。而是存在一種事物,然后在為它創(chuàng)造一個名詞。

比如你看到一頭豬(動物),你會說:“我看到了一頭豬(名詞)”。但是這頭豬(動物)在沒有名詞(豬)的時候就已經(jīng)存在了,在描述沒有名詞的事物時,你可能會“阿巴阿巴阿巴”的敘述一堆對這個事物的描述。但它卻是存在啊,你是通過它所具有的 特質(zhì)(屬性) 來區(qū)分這是一頭豬不是一頭驢的。

在認(rèn)識事物的時候,你是通過屬性來區(qū)分不同事物的,而不是最終表現(xiàn)的名詞。

事物看起來確實(shí)是由這些屬性所構(gòu)成的,那它是由這些屬性來確定唯一性的嗎?

實(shí)體標(biāo)識

entity-and-id
  • A:你說的哪一頭豬???
  • B:就是那一頭??!
  • A:那是哪一頭?
  • B:非常胖的那一頭!
  • A:到底哪一頭?
  • B:滾,算了。

那這頭沒有顯式編號(標(biāo)識)的豬還是不是一個實(shí)體呢?

當(dāng)然是,它必須是。只是你還不知道如何唯一標(biāo)識它。如果此時你說:“那頭編號為 5 的豬。”,這將在描述實(shí)體時變得如此簡單,因?yàn)?A 和 B 都知道 5 號豬是哪一個。就算是不知道,只需要去豬圈里找到 5 號豬即可。

person-lifetime

一個事物從產(chǎn)生,發(fā)展,興盛直至消亡的過程中,它們的形式和內(nèi)容可能一直在發(fā)生變化(有可能是根本性的變化)。在經(jīng)歷這個“成住壞空”的過程中什么是不變的呢?

一個人剛出生時可能只有五六斤那么重,成年以后可能會長到一百三四十斤。剛出生時可能只有四五十厘米那么高,成年以后可能會長到一米七八那么高。剛出生時小臉可能有一些紅暈,成年以后可能會變得俊美。

在這一生中,你的體型、身高、相貌和年齡都在發(fā)生變化,甚至姓名都可能會改變。是什么使你在這茫茫人海中能夠獨(dú)立存在,并且別人能夠認(rèn)出你是誰。甚至在公司里有兩個重名的同事,你依然可以分辨他們誰是誰。但是你在通過名字分辨他們時,你腦海里是關(guān)聯(lián)的那個人本身。他就是他,是不會變得。不管他的體型、身高、相貌和年齡發(fā)生任何根本變化,你依然能識別他,因?yàn)?strong>他就是他。

人類需要解決他就是他的這個問題,標(biāo)識就是為了解決他就是他的這個問題。人類的思考就是這么強(qiáng)悍。

事物本身是具有標(biāo)識的,只是我們還沒有找到如何發(fā)現(xiàn)標(biāo)識的密碼。但是我們知道標(biāo)識是存在的,又無法發(fā)現(xiàn)它時,我們只好給它為事物自定義一個標(biāo)識。

通常標(biāo)識是不變的,它會伴隨一個事物的一生。正是這種不變性唯一性才能貫穿一個事物在整個生命周期中抽象的連續(xù)性

注意: 在對象模型中,標(biāo)識是通過字段(屬性)的方式進(jìn)行實(shí)現(xiàn)的(表示的),定義一個標(biāo)識有時只需要一個字段就夠了,但有時一個字段往往不能唯一標(biāo)識一個實(shí)體,此時我們可能會使用兩個、三個甚至四個字段來明確實(shí)體的唯一性,我們把這種多個字段組合成的標(biāo)識叫做復(fù)合標(biāo)識或者聯(lián)合標(biāo)識。

實(shí)體必須定義標(biāo)識嗎?

entity-can-id

實(shí)體必須是有標(biāo)識的,但是你不一定需要為每一個事物建模時都定義標(biāo)識。

當(dāng)你在商場購買一件衣服時,你會在意一件衣服的標(biāo)識嗎?你只會在意它的品牌、尺碼、顏色、款式等等一系列的屬性,此時商品標(biāo)識顯得就沒有那么重要了,商品標(biāo)識更像是這件衣服的附帶品(附加值)。過了幾天你又去了這家商場,這次不是買,而是換貨。前臺服務(wù)員讓你提供當(dāng)時夠買衣服時的小票,服務(wù)員根據(jù)小票的標(biāo)識查詢到你購買的衣服,最終為你了換貨。

在為小票建模時,小票上的商品標(biāo)識就無法確定唯一性了,因?yàn)槎鄰埿∑笨梢躁P(guān)聯(lián)同一個商品(多對多)。然而這個商品在這一張小票上卻是唯一的,我們可以通過(ReceiptId、ProductId)來定位這個購買的商品。但是我們從不關(guān)心小票上的商品脫離小票后是否唯一。

小票上的商品是實(shí)體嗎?

我們在認(rèn)識實(shí)體時特別強(qiáng)調(diào)了實(shí)體的概念:

實(shí)體是與其他事物分開存在的事物,并且具有自己的明確標(biāo)識。

為一個事物定義了明確的標(biāo)識后,會使你有一種錯覺。你會感覺只要為事物定義明確標(biāo)識后,事物就是實(shí)體或者實(shí)體就是事物了。

但是我們對分開存在的理解還需要強(qiáng)化。

分開存在就意味著需要獨(dú)立存在,小票上的商品對象(Receipt Product)離開小票對象(Receipt)能獨(dú)立存在嗎?

  • 如果能,商品對象(Receipt Product)就是一個實(shí)體。
  • 如果不能,商品對象(Receipt Product)就不是一個實(shí)體。

如果商品對象(Receipt Product)不是實(shí)體,那它是什么?

是對象(Object)啊,這樣的回答你是否能想明白(阿巴阿巴阿巴)。我們一直叫實(shí)體為實(shí)體對象,是因?yàn)閷?shí)體本身就是一個對象啊。不是因?yàn)槊嫦驅(qū)ο蟛沤兴鼘ο螅撬褪菍ο蟛沤兴鼘ο?。對象、?shí)體的概念要比計(jì)算機(jī)對面向?qū)ο?/strong>的應(yīng)用早的多得多。

對象要比實(shí)體的范圍大的多得多,對象包括(實(shí)體),實(shí)體對象只是對實(shí)體的抽象,對象比實(shí)體抽象的“東西”多得多的。

建模:實(shí)體與值對象,做出選擇。

entity-can-id

“我們應(yīng)該盡量使用值對象來建模而不是實(shí)體對象”,我在看到這句話時,確實(shí)驚訝不已。

阿巴阿巴阿巴,去做選擇吧。

實(shí)體行為

對象是由屬性和方法組成。

我們都在試圖理解這句話,有的時候卻一直在相而行。

過去我們的對象上只包含屬性,后來我們意識到這樣是不完整的。然后我們開始為對象抽象方法,添加方法。但是這個過程卻發(fā)生了極化,有一部分人提出只能使用方法來操作對象。

這個過程最終產(chǎn)生了三種方式來操作對象:

  • 只使用屬性來操作對象。
  • 使用屬性和方法來操作對象。
  • 只使用方法來操作對象。

這三種方式到底哪個合理呢?

在《Java 編程思想》中有一個使用電燈(Light)的例子來初次解釋什么是面向?qū)ο蟆?/p>

light-case

這個例子為我們展現(xiàn)了電燈對象具有的行為:開燈(On)、關(guān)燈(Off)、變亮(Brighten)、變暗(Dim),這些行為是本身所具有的

到目前為止這個例子好像確實(shí)是只使用方法來操作對象。

如果現(xiàn)在要求可以任意調(diào)整電燈的顏色(Color),應(yīng)該怎么設(shè)計(jì)了呢?

顏色(Color)是一個屬性(Property),需要改變電燈的顏色。我們瞬間想要為電燈對象提供一個改變顏色(Change Color)的方法。

真的需要為電燈對象提供一個改變顏色(Change Color)的方法嗎?

不一定需要,直接修改屬性(Property)是可以的。

你知道屬性(Property)和字段(Field)的區(qū)別?

屬性(Property)具有封裝性。

有一天你感覺那面墻的顏色有些不好看,你拿著刷子就去刷墻。墻也沒有為你提供一個改變顏色的方法,而你卻直接改變了墻的顏色屬性。

這個方法屬于這個對象嗎?

我曾經(jīng)看到過一個視頻:外國人用白瓷盤切烤制好的乳豬,完成切割后隨手拋出瓷盤摔壞。

你覺得摔壞這個動作屬于盤子對象嗎?如果屬于:

public interface Plate {
    void crash(); // break 摔壞
}

這就好比,你對這一個盤子告訴它,你摔壞自己。盤子本身明顯是不具備自我摔壞這樣的功能的,盤子之所以會被摔壞是外界對它的摧殘。對于實(shí)體對象,本身方法應(yīng)該是對事物本身所具有功能的抽象。

由于在 Java 中不能直接創(chuàng)建函數(shù)(function),所以你只能這樣:

public class Crasher {
    public void crash(Plate plate) {
        // 摔壞它...
        plate.broken(); // 已摔壞
    }
}

開源電商

Mallfoundry 是一個完全開源的使用 Spring Boot 開發(fā)的多商戶電商平臺。它可以嵌入到已有的 Java 程序中,或者作為服務(wù)器、集群、云中的服務(wù)運(yùn)行。

  • 領(lǐng)域模型采用領(lǐng)域驅(qū)動設(shè)計(jì)(DDD)、接口化以及面向?qū)ο笤O(shè)計(jì)。

項(xiàng)目地址:https://gitee.com/mallfoundry/mall

總結(jié)

我們從認(rèn)識實(shí)體中了解到了實(shí)體的概念。并在形成實(shí)體中去引出事物與實(shí)體的關(guān)系,實(shí)體類與實(shí)體的關(guān)系,類與對象的關(guān)系,以及實(shí)體與對象的關(guān)系。又在實(shí)體標(biāo)識中強(qiáng)調(diào)了標(biāo)識對實(shí)體的重要性,以及由強(qiáng)調(diào)了實(shí)體與對象的關(guān)系。最后又加入了一點(diǎn)對實(shí)體行為的擴(kuò)展,對實(shí)體本身的屬性和方法做了一點(diǎn)點(diǎn)的探討。

請記住這句話:實(shí)體是與其他事物分開存在的事物,并且具有自己的明確標(biāo)識。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容