小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

如何運用領(lǐng)域驅(qū)動設(shè)計 - 實體

 python_lover 2020-05-02

概述

本文將介紹領(lǐng)域驅(qū)動設(shè)計(DDD)戰(zhàn)術(shù)模式中另一個常見且非常重要的概念 - 實體。相對戰(zhàn)術(shù)模式中其他的一些概念(例如 值對象、領(lǐng)域服務(wù)等)來說,實體應(yīng)該比較容易讓人理解和運用。但是我們?nèi)绾稳グl(fā)現(xiàn)所在領(lǐng)域中的實體呢?如何保證建立的實體是富含行為的?實體運用時又有那些注意的細(xì)節(jié)呢?本文將從不同的角度來帶大家重新認(rèn)識一下“實體”這個概念,并且給出相應(yīng)的代碼片段(本教程的代碼片段都使用的是C#,后期的實戰(zhàn)項目也是基于 DotNet Core 平臺)。

何為實體

按照國際慣例呢,我們先吹牛。直接來看看原著《領(lǐng)域驅(qū)動設(shè)計:軟件核心復(fù)雜性應(yīng)對之道》 中對實體的解釋:

  • 實體(Entity,又稱為Reference Object) 很多對象不是通過他們的屬性定義的,而是通過一連串的連續(xù)事件和標(biāo)識定義的。
  • 主要由標(biāo)識定義的對象被稱為ENTITY。

上面的兩句話多讀了幾遍,好像這個定義還是能夠理解嘛。不像上一篇文章 如何運用DDD - 值對象 中的概念那么深奧。說白了,上面就是說明了一個問題,只要你所發(fā)現(xiàn)的事物/對象有一個唯一的標(biāo)識,那么它可能就是實體了。而唯一的標(biāo)識就是我們代碼中快寫爛了的那個ID。

似曾相識

來想一下,我們在以傳統(tǒng)的設(shè)計思路和開發(fā)過程中,我們會在什么情況下為一個對象賦予一個ID呢?給它賦予這個ID的作用呢?一般來說我們的目的無非就是 1、為了區(qū)分本對象,如果是在數(shù)據(jù)庫中,那就是為了區(qū)分本條數(shù)據(jù)和另外一條數(shù)據(jù),而這個ID也往往作為主鍵而存在 2、加個索引吧,來提升關(guān)聯(lián)查找速度。所以我們?nèi)绻麑?shù)據(jù)庫中的表映射到我們的代碼中以類的形式呈現(xiàn)的時候,它可能就是這個樣子:

//旅行的行程
public class Itinerary
{
    public int ID { get; set; }

    //參加本次旅行的人員
    public List<Person> Participants { get; set; }

    //旅行的地點
    public List<string> Places { get; set; } 

    //關(guān)于該行程的備注筆記信息
    public string  Note { get; set; } 

    //旅行開始時間
    public DateTime StartTime { get; set; }

    //旅行開始時間
    public DateTime? EndTime { get; set; }

    //旅行的狀態(tài)(進(jìn)行中 or 已完成)
    public int Status { get; set; }
}

上面的代碼對我們來說應(yīng)該絲毫都不陌生,我們建立了一個旅行行程的類,至于為什么我們會選取旅行行程,而不是各個博客都出現(xiàn)的以訂單啊電商平臺作為案例。那是因為在后期我們會一起動手來實現(xiàn)一個旅行記賬的微信小程序,并且借助于我們慢慢所學(xué)習(xí)到的DDD理論作為基礎(chǔ),開發(fā)屬于我們自己的領(lǐng)域驅(qū)動框架,當(dāng)然項目也是基于 DotNet Core(版本應(yīng)該是3.x)。

好了,還是回到我們這個例子,來思考一下ID出現(xiàn)的目的。你可能會說:“這還不簡單嗎?老夫縱橫代碼界多年,你現(xiàn)在還來問我這個問題!ID肯定是用來區(qū)分的呀,行程千千萬萬,我要找出這一條行程肯定需要這個ID了呀?!?是的,這是一個毫無爭議的問題。我們需要一個唯一的身份標(biāo)識來區(qū)別對象之間的差異。DDD中實體的這一點與我們平時所接觸的類的ID有異曲同工之妙,所以本文開頭也說了實體可能是相對其他戰(zhàn)術(shù)概念最為讓人理解的。

你確定它真的需要ID嗎

還記得我們在上一篇文章 如何運用DDD - 值對象 中所提到過的一個問題嗎? “當(dāng)前上下文的值對象可能是另一個上下文的實體”。所以說,當(dāng)前你所判定的實體一定是基于領(lǐng)域當(dāng)前環(huán)境(上下文)的。脫離了該環(huán)境之后,一切都將存在變數(shù)。同樣的事物(對象),在當(dāng)前環(huán)境需要一個唯一標(biāo)識來識別它,而在另一個環(huán)境中可能這個唯一標(biāo)識對它來說是沒有意義的,則實體就有可能成為了值對象。請考慮下面的這個例子:

在一個銀行業(yè)應(yīng)用程序中,一位顧客可能會在她的銀行賬戶中放入100美元。當(dāng)她未來某一天提取她這100美元時,相較于她存進(jìn)銀行的錢,她可能會收到不同的鈔票或硬幣。不過,這一差異是無關(guān)緊要的,因為資金的身份不重要;顧客只關(guān)心資金的價值。所以在這個領(lǐng)域中,資金無疑是一個值對象。但在另一個領(lǐng)域中,比如涉及鈔票印刷制作或鈔票可追溯性的行業(yè),個體鈔票或硬幣的身份實際上可能就是一個重要的領(lǐng)域概念了。所以每一張鈔票都會是一個具有唯一標(biāo)識符的實體

運用實體

結(jié)合值對象

千萬不要忘記了我們上一章所學(xué)習(xí)到了的值對象:在實體的內(nèi)部,除了它自己的唯一標(biāo)識ID之外,也許還有許許多多表明它屬性的東西,而這些東西往往可以通過使用值對象來標(biāo)識。
接下來讓我們來改寫一下上面的Itinerary類:

public class Itinerary
{
    public int ID { get; set; }

    public List<Person> Participants { get; set; }

    public List<Address> Places { get; set; } 

    public ItineraryNote  Note { get; set; } 

    public ItineraryTime TripTime { get; set; }

    public ItineraryStatus Status { get; set; }
}

public class ItineraryNote
{
    public string Content { get; set; }
    public DateTime NoteTime { get; set; }

    public ItineraryNote(string content)
    {
        Content = content;
        NoteTime = DateTime.Now;
    }
}

為實體賦予它的行為

當(dāng)對象建立好了之后,為了實現(xiàn)我們的業(yè)務(wù)邏輯處理,我們需要對實例化的對象進(jìn)行操作?,F(xiàn)在我們?yōu)樵撓到y(tǒng)提出第一個需求:用戶可以修改行程中的備注信息。
回到我們的第一版代碼中,如果我們需要處理這個操作,我們會怎么做呢?

itineraryInstance.Note = "this is my new note info";

是不是會像上面這樣,將需要添加的值賦予實例化的對象呢。 這種操作,對我們現(xiàn)在正在進(jìn)行的編程習(xí)慣來說,是再正常不過了。

那么我們來思考,如果我們的項目有多處需要對“備注信息”處理呢。則對該屬性的變更將被散落在代碼各處。而當(dāng)我們對該需求進(jìn)行了一個增強驗證時,比如此時我們需要增加:用戶修改行程中的備注信息時,只允許用戶錄入200個字以內(nèi)的文本。 OMG,此時我們需要去查找所有散落的片段,并且為他加上驗證。

從另外個角度來看,第一個版本我們所建立的類,我們無法通過僅僅查看它本身就能讀懂有關(guān)旅行行程有關(guān)的業(yè)務(wù),我們僅僅知道它具有起始時間,備注信息等,而對他們應(yīng)該如何相互作用無從所知。
所以這種僅僅具有類的屬性,或者說以POCO呈現(xiàn)的類型,我們稱之為“貧血模型”。

接下來,我們回到第二版代碼中,我們?yōu)樗x予屬于它的行為。從需求中我們得知了,行程的備注信息是可以修改的,而備注信息是屬于行程的,因此修改備注信息改行為理應(yīng)屬于行程本身。我們稍微改動代碼:

public class Itinerary
{
    public int ID { get; set; }

    public List<Person> Participants { get; set; }

    public List<Address> Places { get; set; } 

    public ItineraryNote  Note { get; set; } 

    public ItineraryTime TripTime { get; set; }

    public ItineraryStatus Status { get; set; }

    //ctor

    public void ChangeNote(string content)
    {
        if(content.Length > 200 )
            throw new NoteIsOverlengthException();
        Note  = new ItineraryNote(content);
    }
}

此時我們?yōu)?strong>Itinerary賦予了一個ChangeNote的行為,當(dāng)外界需要更改備注時,則只需通過調(diào)用改方法既可以實現(xiàn),而且當(dāng)展開其他開發(fā)人員閱讀此類時,也會清楚的明白,業(yè)務(wù)上允許用戶更改200字以內(nèi)的備注。

但是,我們依然有一個地方美中不足,我想你可能也發(fā)現(xiàn)了:屬性還是對外暴露的! 對,也就是說,我們除了通過類公開的行為修改類自身的屬性外,我們還可以在外界隨意更改。這顯然不符合我們設(shè)計的初衷。因此我們可以將所有屬性的set私有化。所以,一定要注意,我們在考慮實體的時候,一定要知道“實體是高度內(nèi)聚和自治的”(敲重點?。。。。。?。

當(dāng)然,有的開發(fā)者還會嘗試另外的寫法,讓實體完全自治,將上面的代碼中的屬性,全部轉(zhuǎn)變?yōu)樗接械淖侄危饨缰荒芡ㄟ^公開的行為來對實體進(jìn)行處理。

public class Itinerary
{
    public int ID { get; set; }

    private List<Person> participants;

    private List<Address> places;

    private ItineraryNote  note;

    private ItineraryTime tripTime;

    private ItineraryStatus status;

    //ctor

    public void ChangeNote(string content)
    {
        if(content.Length > 200 )
            throw new NoteIsOverlengthException();
        note  = new ItineraryNote(content);
    }
}

但是當(dāng)外界需要獲取該實體的值,或者需要ORM映射的時候可能就不是很友好了,不過你可以使用類似于像 備忘錄模式 的快照方法來處理。后期我們也會采用這種模式來實現(xiàn)部分案例。

通過將實體賦予它應(yīng)用的行為所建立出來的實體我們稱為“充血模型”。那么貧血模型好還是充血模型好呢? 很多同學(xué)肯定會說,這還用問嗎,肯定是充血模型啦。 其實這個答案并沒有一個真正的答案,實體自身的行為是通過我們對領(lǐng)域的慢慢分析(可能是通過與領(lǐng)域?qū)<覝贤ǎ┑脕淼?,如果因為為了使用充血模型而盲目的將一些不屬于實體的行為賦予給它,只會讓實體變的更加混亂,從而得不償失。所以,此時的貧血模型并不意味著一直是貧血模型,后期隨著領(lǐng)域的深入它可能會不斷豐富屬于自身的行為。

嘗試轉(zhuǎn)移一部分行為給值對象

保持實體專注于身份這一職責(zé)很重要,因為這樣會避免它們變得臃腫————這是它們將許多相關(guān)行為拉到一起時容易掉入的陷阱。實現(xiàn)這一專注需要將相關(guān)行為委托給值對象和領(lǐng)域服務(wù)(領(lǐng)域服務(wù)也將在后期的文章中進(jìn)行介紹)。
來考慮一下最近一版的代碼,我們已經(jīng)將行為劃分給了Itinerary了,但是仔細(xì)看一看,我們在后期增加需求時增加了一條驗證的規(guī)則,那么這個規(guī)則我們可以轉(zhuǎn)移給值對象嗎? 答案是,可以的。而且轉(zhuǎn)移是有必要的,因為對備注的效驗這一行為往往應(yīng)該屬于它自身。就好比機器啟動時的自我效驗,這一行為是屬于操作者還是機器自己呢?
所以我們來將部分行為轉(zhuǎn)移給值對象,優(yōu)化后的代碼可能是這樣的:

public class Itinerary
{
    public int ID { get; set; }

    public List<Person> Participants { get; set; }

    public List<Address> Places { get; set; } 

    public ItineraryNote  Note { get; set; } 

    public ItineraryTime TripTime { get; set; }

    public ItineraryStatus Status { get; set; }

    //ctor

    public void ChangeNote(string content)
    {
        Note  = new ItineraryNote(content);
    }
}

public class ItineraryNote
{
    public string Content { get; set; }
    public DateTime NoteTime { get; set; }

    public ItineraryNote(string content)
    {
        if(content.Length > 200 )
            throw new NoteIsOverlengthException();
        Content = content;
        NoteTime = DateTime.Now;
    }
}

愿景是美好的 現(xiàn)實是殘酷的

到這里,我們仿佛真的一帆風(fēng)順:建立了屬于自己的實體,并且融合了該有的值對象,實體的行為也被高度內(nèi)聚在了其中。那是不是我們直接就可以將DDD落地了呢? 不好意思,就如同這個小標(biāo)題一樣,現(xiàn)實真的是非常殘酷的。如果單單從代碼閱讀和業(yè)務(wù)處理上來說,我們可能確實已經(jīng)成功了,但是!??!我們需要保存我們的數(shù)據(jù),也就是持久化。因為實體中包含了大量的值對象,所有值對象持久化所面臨的問題,它都會遇到,甚至是讓難度翻倍!有關(guān)值對象持久化的難點可以參考上一篇文章 如何運用DDD - 值對象 。

回看我們最后一版代碼,我們有兩個集合的屬性(Participants、Places)。單一的值對象的持久化已經(jīng)讓我們頭痛了,現(xiàn)在我們不得不面對持久化值對象集合的問題。假如你通過使用EF Core這類的ORM框架來進(jìn)行持久化操作,你會發(fā)現(xiàn)我們不得不為List中的值對象加上一個ID,此時擁有了唯一標(biāo)示的值對象顯然已經(jīng)成為了實體,這是非??膳碌囊患隆N覀冃列量嗫嘟⒌念I(lǐng)域模型在最后一步落地時居然成為改變了,這往往也是DDD落地困難的一個重要原因,被ORM框架或者關(guān)系型數(shù)據(jù)庫所限制,導(dǎo)致領(lǐng)域模型不斷被打亂,重構(gòu)領(lǐng)域模型變得越來越四不像,最終又寫回了傳統(tǒng)的三層架構(gòu)或者面向數(shù)據(jù)庫建模。

但是至少在現(xiàn)在,請相信自己的所見,認(rèn)真考慮和發(fā)現(xiàn)你項目領(lǐng)域所擁有的值對象和實體,不要因為知道持久化的問題而放棄和妥協(xié),這也是我們開發(fā)者應(yīng)有的勇氣。在后面的文章中,我們會關(guān)于值對象和實體的一些問題提出解決辦法,當(dāng)然包括持久化的問題。

總結(jié)

本文我們介紹了實體的概念以及怎么去運用實體到實際代碼中,請牢記前人為我們提供的有關(guān)實體的經(jīng)驗:比如“實體一定是基于領(lǐng)域當(dāng)前環(huán)境(上下文)的”“實體是高度內(nèi)聚和自治的”、“應(yīng)該專注于實體的行為而非數(shù)據(jù)”等等。后面的文章會為大家?guī)韺嶓w和值對象的一些注意事項以及領(lǐng)域服務(wù)的內(nèi)容。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多