DCI in C++


DCI是一種面向?qū)ο筌浖軜?gòu)模式,它可以讓面向?qū)ο蟾玫貙?duì)數(shù)據(jù)和行為之間的關(guān)系進(jìn)行建模從而更容易被人理解。DCI目前廣泛被作為對(duì)DDD(領(lǐng)域驅(qū)動(dòng)開發(fā))的一種發(fā)展和補(bǔ)充,用于基于面向?qū)ο蟮念I(lǐng)域建模。

DCI建議將軟件的領(lǐng)域核心代碼分為Context、Interactive和Data層。Context層用于處理由外部UI或者消息觸發(fā)業(yè)務(wù)場(chǎng)景,每個(gè)業(yè)務(wù)場(chǎng)景都能找對(duì)一個(gè)對(duì)應(yīng)的context,其作為理解系統(tǒng)如何處理業(yè)務(wù)流程的起點(diǎn)。Data層用來描述系統(tǒng)是什么(What the system is?),在該層中采用領(lǐng)域驅(qū)動(dòng)開發(fā)中描述的建模技術(shù),識(shí)別系統(tǒng)中應(yīng)該有哪些領(lǐng)域?qū)ο笠约斑@些對(duì)象的生命周期和關(guān)系。而DCI最大的發(fā)展則在于Interactive層,DCI認(rèn)為應(yīng)該顯示地對(duì)領(lǐng)域?qū)ο笤诿總€(gè)context中所扮演的角色role進(jìn)行建模,role代表了領(lǐng)域?qū)ο蠓?wù)于context時(shí)應(yīng)該具有的業(yè)務(wù)行為。正是因?yàn)轭I(lǐng)域?qū)ο蟮臉I(yè)務(wù)行為只有在去服務(wù)于某一context時(shí)才會(huì)具有意義,DCI認(rèn)為對(duì)role的建模應(yīng)該是面向context的,屬于role的方法不應(yīng)該強(qiáng)塞給領(lǐng)域?qū)ο?,否則領(lǐng)域?qū)ο缶蜁?huì)隨著其支持的業(yè)務(wù)場(chǎng)景(context)越來越多而變成上帝類。但是role最終還是要操作數(shù)據(jù),那么role和領(lǐng)域?qū)ο笾g應(yīng)該存在一種注入(cast)關(guān)系。當(dāng)context被觸發(fā)的時(shí)候,context串聯(lián)起一系列的role進(jìn)行交互完成一個(gè)特定的業(yè)務(wù)流程。Context應(yīng)該決定在當(dāng)前業(yè)務(wù)場(chǎng)景下每個(gè)role的扮演者(領(lǐng)域?qū)ο螅琧ontext中僅完成領(lǐng)域?qū)ο蟮絩ole的注入或者cast,然后讓role互動(dòng)以完成對(duì)應(yīng)業(yè)務(wù)邏輯。

基于上述DCI的特點(diǎn),DCI架構(gòu)使得軟件具有如下好處:

  • 清晰的進(jìn)行了分層使得軟件更容易被理解。

    1. Context是盡可能薄的一層。Context往往被實(shí)現(xiàn)得無狀態(tài),只是找到合適的role,讓role交互起來完成業(yè)務(wù)邏輯即可。但是簡(jiǎn)單并不代表不重要,顯示化context層正是為人去理解軟件業(yè)務(wù)流程提供切入點(diǎn)和主線。
    2. Data層描述系統(tǒng)有哪些領(lǐng)域概念及其之間的關(guān)系,該層專注于領(lǐng)域?qū)ο蠛椭g關(guān)系的確立,讓程序員站在對(duì)象的角度思考系統(tǒng),從而讓系統(tǒng)是什么更容易被理解。
    3. Interactive層主要體現(xiàn)在對(duì)role的建模,role是每個(gè)context中復(fù)雜的業(yè)務(wù)邏輯的真正執(zhí)行者。Role所做的是對(duì)行為進(jìn)行建模,它聯(lián)接了context和領(lǐng)域?qū)ο螅∮捎谙到y(tǒng)的行為是復(fù)雜且多變的,role使得系統(tǒng)將穩(wěn)定的領(lǐng)域模型層和多變的系統(tǒng)行為層進(jìn)行了分離,由role專注于對(duì)系統(tǒng)行為進(jìn)行建模。該層往往關(guān)注于系統(tǒng)的可擴(kuò)展性,更加貼近于軟件工程實(shí)踐,在面向?qū)ο笾懈嗟氖且灶惖囊暯沁M(jìn)行思考設(shè)計(jì)。
  • 顯示的對(duì)role進(jìn)行建模,解決了面向?qū)ο蠼V谐溲拓氀P椭疇?zhēng)。DCI通過顯示的用role對(duì)行為進(jìn)行建模,同時(shí)讓role在context中可以和對(duì)應(yīng)的領(lǐng)域?qū)ο筮M(jìn)行綁定(cast),從而既解決了數(shù)據(jù)邊界和行為邊界不一致的問題,也解決了領(lǐng)域?qū)ο笾袛?shù)據(jù)和行為高內(nèi)聚低耦合的問題。

面向?qū)ο蠼C媾R的一個(gè)棘手問題是數(shù)據(jù)邊界和行為邊界往往不一致。遵循模塊化的思想,我們通過類將行為和其緊密耦合的數(shù)據(jù)封裝在一起。但是在復(fù)雜的業(yè)務(wù)場(chǎng)景下,行為往往跨越多個(gè)領(lǐng)域?qū)ο?,這樣的行為放在某一個(gè)對(duì)象中必然導(dǎo)致別的對(duì)象需要向該對(duì)象暴漏其內(nèi)部狀態(tài)。所以面向?qū)ο蟀l(fā)展的后來,領(lǐng)域建模出現(xiàn)兩種派別之爭(zhēng),一種傾向于將跨越多個(gè)領(lǐng)域?qū)ο蟮男袨榻T谒^的service中(見DDD中所描述的service建模元素)。這種做法使用過度經(jīng)常導(dǎo)致領(lǐng)域?qū)ο笞兂芍惶峁┮欢裧et方法的啞對(duì)象,這種建模導(dǎo)致的結(jié)果被稱之為貧血模型。而另一派則堅(jiān)定的認(rèn)為方法應(yīng)該屬于領(lǐng)域?qū)ο?,所以所有的業(yè)務(wù)行為仍然被放在領(lǐng)域?qū)ο笾校@樣導(dǎo)致領(lǐng)域?qū)ο箅S著支持的業(yè)務(wù)場(chǎng)景變多而變成上帝類,而且類內(nèi)部方法的抽象層次很難一致。另外由于行為邊界很難恰當(dāng),導(dǎo)致對(duì)象之間數(shù)據(jù)訪問關(guān)系也比較復(fù)雜。這種建模導(dǎo)致的結(jié)果被稱之為充血模型。

在DCI架構(gòu)中,如何將role和領(lǐng)域?qū)ο筮M(jìn)行綁定,根據(jù)語言特點(diǎn)做法不同。對(duì)于動(dòng)態(tài)語言,可以在運(yùn)行時(shí)進(jìn)行綁定。而對(duì)于靜態(tài)語言,領(lǐng)域?qū)ο蠛蛂ole的關(guān)系在編譯階段就得確定。

DCI的論文《www.artima.com/articles/dci_vision.html》中介紹了C++采用模板Trait的技巧進(jìn)行role和領(lǐng)域?qū)ο蟮慕壎?。但是由于在?fù)雜的業(yè)務(wù)場(chǎng)景下role之間會(huì)存在大量的行為依賴關(guān)系,如果采用模板技術(shù)會(huì)產(chǎn)生復(fù)雜的模板交織代碼從而讓工程層面變得難以實(shí)施。正如我們前面所講,role主要對(duì)復(fù)雜多變的業(yè)務(wù)行為進(jìn)行建模,所以role需要更加關(guān)注于系統(tǒng)的可擴(kuò)展性,更加貼近軟件工程,對(duì)role的建模應(yīng)該更多地站在類的視角,而面向?qū)ο蟮亩鄳B(tài)和依賴注入則可以相對(duì)更輕松地解決此類問題。另外,由于一個(gè)領(lǐng)域?qū)ο罂赡軙?huì)在不同的context下扮演多種角色,這時(shí)領(lǐng)域?qū)ο笠軌蚝投喾N不同類型的role進(jìn)行綁定。對(duì)于所有這些問題,CUB提供的DCI框架采用了多重繼承來描述領(lǐng)域?qū)ο蠛推渲С值膔ole之間的綁定關(guān)系,同時(shí)采用了在多重繼承樹內(nèi)進(jìn)行關(guān)系交織來進(jìn)行role之間的依賴關(guān)系描述。這種方式在C++中比采用傳統(tǒng)的依賴注入的方式更加簡(jiǎn)單高效。

對(duì)于DCI的理論介紹,以及如何利用DCI框架進(jìn)行領(lǐng)域建模,本文就介紹這些。后面主要介紹如何利用CUB中的DCI框架來實(shí)現(xiàn)和拼裝role以完成這種組合式編程。

下面假設(shè)一種場(chǎng)景:模擬人和機(jī)器人制造產(chǎn)品。人制造產(chǎn)品會(huì)消耗吃飯得到的能量,缺乏能量后需要再吃飯補(bǔ)充;而機(jī)器人制造產(chǎn)品會(huì)消耗電能,缺乏能量后需要再充電。這里人和機(jī)器人在工作時(shí)都是一名worker(扮演的角色),工作的流程是一樣的,但是區(qū)別在于依賴的能量消耗和獲取方式不同。

DEFINE_ROLE(Energy)
{
    ABSTRACT(void consume());
    ABSTRACT(bool isExhausted() const);
};

struct HumanEnergy : Energy
{
    HumanEnergy()
    : isHungry(false), consumeTimes(0)
    {
    }

private:
    OVERRIDE(void consume())
    {
        consumeTimes++;

        if(consumeTimes >= MAX_CONSUME_TIME)
        {
            isHungry = true;
        }
    }

    OVERRIDE(bool isExhausted() const)
    {
        return isHungry;
    }

private:
    enum
    {
        MAX_CONSUME_TIME = 10,
    };

    bool isHungry;
    U8 consumeTimes;
};

struct ChargeEnergy : Energy
{
    ChargeEnergy() : percent(0)
    {
    }

    void charge()
    {
        percent = FULL_PERCENT;
    }

private:
    OVERRIDE(void consume())
    {
        if(percent > 0)
            percent -= CONSUME_PERCENT;
    }

    OVERRIDE(bool isExhausted() const)
    {
        return percent == 0;
    }

private:
    enum
    {
        FULL_PERCENT = 100,
        CONSUME_PERCENT = 1
    };

    U8 percent;
};

DEFINE_ROLE(Worker)
{
    Worker() : produceNum(0)
    {
    }

    void produce()
    {
        if(ROLE(Energy).isExhausted()) return;

        produceNum++;

        ROLE(Energy).consume();
    }

    U32 getProduceNum() const
    {
        return produceNum;
    }

private:
    U32 produceNum;

private:
    USE_ROLE(Energy);
};

上面代碼中使用了DCI框架中三個(gè)主要的語法糖:

  • DEFINE_ROLE:用于定義role。DEFINE_ROLE的本質(zhì)是創(chuàng)建一個(gè)包含了虛析構(gòu)的抽象類,但是在DCI框架里面使用這個(gè)命名更具有語義。DEFINE_ROLE定義的類中需要至少包含一個(gè)虛方法或者使用了USE_ROLE聲明依賴另外一個(gè)role。

  • USE_ROLE:在一個(gè)類里面聲明自己的實(shí)現(xiàn)依賴另外一個(gè)role。

  • ROLE:當(dāng)一個(gè)類聲明中使用了USE_ROLE聲明依賴另外一個(gè)類XXX后,則在類的實(shí)現(xiàn)代碼里面就可以調(diào)用 ROLE(XXX)來引用這個(gè)類去調(diào)用它的成員方法。

上面的例子中用DEFINE_ROLE定義了一個(gè)名為Worker的role(本質(zhì)上是一個(gè)類),WorkerUSE_ROLE聲明它的實(shí)現(xiàn)需要依賴于另一個(gè)role:EnergyWorker在它的實(shí)現(xiàn)中調(diào)用ROLE(Energy)訪問它提供的接口方法。Energy是一個(gè)抽象類,有兩個(gè)子類HumanEnergyChargeEnergy分別對(duì)應(yīng)于人和機(jī)器人的能量特征。上面是以類的形式定義的各種role,下面我們需要將role和領(lǐng)域?qū)ο箨P(guān)聯(lián)并將role之間的依賴關(guān)系在領(lǐng)域?qū)ο髢?nèi)完成正確的交織。

struct Human : Worker
             , private HumanEnergy
{
private:
    IMPL_ROLE(Energy);
};

struct Robot : Worker
             , ChargeEnergy
{
private:
    IMPL_ROLE(Energy);
};

上面的代碼使用多重繼承完成了領(lǐng)域?qū)ο髮?duì)role的組合。在上例中Human組合了WorkerHumanEnergy,而Robot組合了WorkerChargeEnergy。最后在領(lǐng)域?qū)ο蟮念悆?nèi)還需要完成role之間的關(guān)系交織。由于Worker中聲明了USE_ROLE(Energy),所以當(dāng)HumanRobot繼承了Worker之后就需要顯示化Energy從哪里來。有如下幾種主要的交織方式:

  • IMPL_ROLE: 對(duì)上例,如果Energy的某一個(gè)子類也被繼承的話,那么就直接在交織類中聲明IMPL_ROLE(Energy)。于是當(dāng)Worker工作時(shí)所找到的ROLE(Energy)就是在交織類中所繼承的具體Energy子類。

  • IMPL_ROLE_WITH_OBJ: 當(dāng)持有被依賴role的一個(gè)引用或者成員的時(shí)候,使用IMPL_ROLE_WITH_OBJ進(jìn)行關(guān)系交織。假如上例中Human類中有一個(gè)成員:HumanEnergy energy,那么就可以用IMPL_ROLE_WITH_OBJ(Energy, energy)來聲明交織關(guān)系。該場(chǎng)景同樣適用于類內(nèi)持有的是被依賴role的指針、引用的場(chǎng)景。

  • DECL_ROLE : 自定義交織關(guān)系。例如對(duì)上例在Human中定義一個(gè)方法DECL_ROLE(Energy){ // function implementation},自定義Energy的來源,完成交織。

當(dāng)正確完成role的依賴交織工作后,領(lǐng)域?qū)ο箢惥涂梢员粚?shí)例化了。如果沒有交織正確,一般會(huì)出現(xiàn)編譯錯(cuò)誤。

TEST(...)
{
    Human human;
    SELF(human, Worker).produce();
    ASSERT_EQ(1, SELF(human, Worker).getProduceNum());

    Robot robot;
    SELF(robot, ChargeEnergy).charge();
    while(!SELF(robot, Energy).isExhausted())
    {
        SELF(robot, Worker).produce();
    }
    ASSERT_EQ(100, SELF(robot, Worker).getProduceNum());
}

如上使用SELF將領(lǐng)域?qū)ο骳ast到對(duì)應(yīng)的role上訪問其接口方法。注意只有被public繼承的role才可以從領(lǐng)域?qū)ο笊蟘ast過去,private繼承的role往往是作為領(lǐng)域?qū)ο蟮膬?nèi)部依賴(上例中human不能做SELF(human, Energy)轉(zhuǎn)換,會(huì)編譯錯(cuò)誤)。

通過對(duì)上面例子中使用DCI的方式進(jìn)行分析,我們可以看到CUB提供的DCI實(shí)現(xiàn)方式具有如下特點(diǎn):

  • 通過多重繼承的方式,同時(shí)完成了類的組合以及依賴注入。被繼承在同一顆繼承樹上的類天然被組合在一起,同時(shí)通過USE_ROLEIMPL_ROLE的這種編織虛函數(shù)表的方式完成了這些類之間的互相依賴引用,相當(dāng)于完成了依賴注入,只不過這種依賴注入成本更低,表現(xiàn)在C++上來說就是避免了在類中去定義依賴注入的指針以及通過構(gòu)造函數(shù)進(jìn)行注入操作,而且同一個(gè)領(lǐng)域?qū)ο箢惖乃袑?duì)象共享類的虛表,所以更加節(jié)省內(nèi)存。

  • 提供一種組合式編程風(fēng)格。USE_ROLE可以聲明依賴一個(gè)具體類或者抽象類。當(dāng)一個(gè)類的一部分有復(fù)用價(jià)值的時(shí)候就可以將其拆分出來,然后讓原有的類USE_ROLE它,最后通過繼承再組合在一起。當(dāng)一個(gè)類出現(xiàn)新的變化方向時(shí),就可以讓當(dāng)前類USE_ROLE一個(gè)抽象類,最后通過繼承抽象類的不同子類來完成對(duì)變化方向的選擇。最后如果站在類的視圖上看,我們得到的是一系列可被復(fù)用的類代碼素材庫;站在領(lǐng)域?qū)ο蟮慕嵌壬蟻砜?,所謂領(lǐng)域?qū)ο笾皇沁x擇合適自己的類素材,最后完成組合拼裝而已(見下面的類視圖和DCI視圖)。

    類視圖:


    DCI視圖:


  • 每個(gè)領(lǐng)域?qū)ο蟮慕Y(jié)構(gòu)類似一顆向上生長(zhǎng)的樹(見上DCI視圖)。Role作為這顆樹的葉子,實(shí)際上并不區(qū)分是行為類還是數(shù)據(jù)類,都盡量設(shè)計(jì)得高內(nèi)聚低耦合,采用USE_ROLE的方式聲明互相之間的依賴關(guān)系。領(lǐng)域?qū)ο笞鳛闃涓捎枚嘀乩^承完成對(duì)role的組合和依賴關(guān)系交織,可以被外部使用的role被public繼承,我們叫做“public role”(上圖中空心圓圈表示),而只在樹的內(nèi)部被調(diào)用的role則被private繼承,叫做“private role”(上圖中實(shí)心圓圈表示)。當(dāng)context需要調(diào)用某一領(lǐng)域?qū)ο髸r(shí),必須從領(lǐng)域?qū)ο骳ast到對(duì)應(yīng)的public role上去調(diào)用,不會(huì)出現(xiàn)傳統(tǒng)教科書上所說的多重繼承帶來的二義性問題。

  • 采用這種多重繼承的方式組織代碼,我們會(huì)得到一種小類大對(duì)象的結(jié)構(gòu)。所謂小類,指的是每個(gè)role的代碼是為了完成組合和擴(kuò)展性,是站在類的角度去解決工程性問題(面向?qū)ο?/strong>),一般都相對(duì)較小。而當(dāng)不同的role組合到一起形成大領(lǐng)域?qū)ο蠛螅鼌s可以讓我們站在領(lǐng)域的角度去思考問題,關(guān)注領(lǐng)域?qū)ο笳w的領(lǐng)域概念、關(guān)系和生命周期(基于對(duì)象)。大對(duì)象的特點(diǎn)同時(shí)極大的簡(jiǎn)化了領(lǐng)域?qū)ο蠊S的成本,避免了繁瑣的依賴注入,并使得內(nèi)存規(guī)劃和管理變得簡(jiǎn)單;程序員只用考慮領(lǐng)域?qū)ο笳w的內(nèi)存規(guī)劃,對(duì)領(lǐng)域?qū)ο笊系乃衦ole整體內(nèi)存申請(qǐng)和釋放,避免了對(duì)一堆小的拼裝類對(duì)象的內(nèi)存管理,這點(diǎn)對(duì)于嵌入式開發(fā)非常關(guān)鍵。

  • 多重繼承關(guān)系讓一個(gè)領(lǐng)域?qū)ο罂梢灾С帜男┙巧╮ole),以及一個(gè)角色可由哪些領(lǐng)域?qū)ο蟀缪葑兊蔑@示化。這種顯示化關(guān)系對(duì)于理解代碼和靜態(tài)檢查都非常有幫助。

上述在C++中通過多重繼承來實(shí)現(xiàn)DCI架構(gòu)的方式,是一種幾近完美的一種方式。如果非要說缺點(diǎn),只有一個(gè),就是多重繼承造成的物理依賴污染問題。由于C++中要求一個(gè)類如果繼承了另一個(gè)類,當(dāng)前類的文件里必須包含被繼承類的頭文件。這就導(dǎo)致了領(lǐng)域?qū)ο箢惖穆暶魑募锩媸聦?shí)上包含了所有它繼承下來的role的頭文件。在context中使用某一個(gè)role需用領(lǐng)域?qū)ο笞鯿ast,所以需要包含領(lǐng)域?qū)ο箢惖念^文件。那么當(dāng)領(lǐng)域?qū)ο笊系娜魏我粋€(gè)role的頭文件發(fā)生了修改,所有包含該領(lǐng)域?qū)ο箢^文件的context都得要重新編譯,無關(guān)該context是否真的使用了被修改的role。解決該問題的一個(gè)方法就是再建立一個(gè)抽象層專門來做物理依賴隔離。例如對(duì)上例中的Human,可以修改如下:

DEFINE_ROLE(Human)
{
    HAS_ROLE(Worker);
};

struct HumanObject : Human
                   , private Worker
                   , private HumanEnergy
{
private:
    IMPL_ROLE(Worker);
    IMPL_ROLE(Energy);
};

struct HumanFactory
{
    static Human* create()
    {
        return new HumanObject;
    }
};

TEST(...)
{
    Human* human = HumanFactory::create();

    human->ROLE(Worker).produce();

    ASSERT_EQ(1, human->ROLE(Worker).getProduceNum());

    delete human;
}

為了屏蔽物理依賴,我們把Human變成了一個(gè)純接口類,它里面聲明了該領(lǐng)域?qū)ο罂杀籧ontext訪問的所有public role,由于在這里只用前置聲明,所以無需包含任何role的頭文件。而對(duì)真正繼承了所有role的領(lǐng)域?qū)ο?code>HumanObject的構(gòu)造隱藏在工廠里面。Context中持有從工廠中創(chuàng)建返回的Human指針,于是context中只用包含Human的頭文件和它實(shí)際要使用的role的頭文件,這樣和它無關(guān)的role的修改不會(huì)引起該context的重新編譯。

事實(shí)上C++語言的RTTI特性同樣可以解決上述問題。該方法需要領(lǐng)域?qū)ο箢~外繼承一個(gè)公共的虛接口類。Context持有這個(gè)公共的接口,利用dynamic_cast從公共接口往自己想要使用的role上去嘗試cast。這時(shí)context只用包含該公共接口以及它僅使用的role的頭文件即可。修改后的代碼如下:

DEFINE_ROLE(Actor)
{
};

struct HumanObject : Actor
                   , Worker
                   , private HumanEnergy
{
private:
    IMPL_ROLE(Energy);
};

struct HumanFactory
{
    static Actor* create()
    {
        return new HumanObject;
    }
};

TEST(...)
{
    Actor* actor = HumanFactory::create();

    Worker* worker = dynamic_cast<Worker*>(actor);

    ASSERT_TRUE(__notnull__(worker));

    worker->produce();

    ASSERT_EQ(1, worker->getProduceNum());

    delete actor;
}

上例中我們定義了一個(gè)公共類Actor,它沒有任何代碼,但是至少得有一個(gè)虛函數(shù)(RTTI要求),使用DEFINE_ROLE定義的類會(huì)自動(dòng)為其增加一個(gè)虛析構(gòu)函數(shù),所以Actor滿足要求。最終領(lǐng)域?qū)ο罄^承Actor,而context僅需持有領(lǐng)域?qū)ο蠊S返回的Actor的指針。Context中通過dynamic_castactor指針轉(zhuǎn)型成領(lǐng)域?qū)ο笊砩掀渌行У膒ublic role,dynamic_cast會(huì)自動(dòng)識(shí)別這種轉(zhuǎn)換是否可以完成,如果在當(dāng)前Actor的指針對(duì)應(yīng)的對(duì)象的繼承樹上找不到目標(biāo)類,dynamic_cast會(huì)返回空指針。上例中為了簡(jiǎn)單把所有代碼寫到了一起。真實(shí)場(chǎng)景下,使用ActorWorker的context的實(shí)現(xiàn)文件中僅需要包含ActorWorker的頭文件即可,不會(huì)被HumanObject繼承的其它role物理依賴污染。

通過上例可以看到使用RTTI的解決方法是比較簡(jiǎn)單的,可是這種簡(jiǎn)單是有成本的。首先編譯器需要在虛表中增加很多類型信息,以便可以完成轉(zhuǎn)換,這會(huì)增加目標(biāo)版本的大小。其次dynamic_cast會(huì)隨著對(duì)象繼承關(guān)系的復(fù)雜變得性能底下。所以C++編譯器對(duì)于是否開啟RTTI有專門的編譯選項(xiàng)開關(guān),由程序員自行進(jìn)行取舍。

最后我們介紹CUB的DCI框架中提供的一種RTTI的替代工具,它可以模仿完成類似dynamic_cast的功能,但是無需在編譯選項(xiàng)中開啟RTTI功能。這樣當(dāng)我們想要在代碼中小范圍使用該特性的時(shí)候,就不用承擔(dān)整個(gè)版本都因RTTI帶來的性能損耗。利用這種替代技術(shù),可以讓程序員精確地在開發(fā)效率和運(yùn)行效率上進(jìn)行控制和平衡。

UNKNOWN_INTERFACE(Worker, 0x1234)
{
// Original implementation codes of Worker!
};

struct HumanObject : dci::Unknown
                   , Worker
                   , private HumanEnergy
{
    BEGIN_INTERFACE_TABLE()
        __HAS_INTERFACE(Worker)
    END_INTERFACE_TABLE()

private:
    IMPL_ROLE(Energy);
};

struct HumanFactory
{
    static dci::Unknown* create()
    {
        return new HumanObject;
    }
};

TEST(...)
{
    dci::Unknown* unknown = HumanFactory::create();

    Worker* worker = dci::unknown_cast<Worker>(unknown);

    ASSERT_TRUE(__notnull__(worker));

    worker->produce();

    ASSERT_EQ(1, worker->getProduceNum());

    delete unknown;
}

通過上面的代碼,可以看到CUB的dci框架中提供了一個(gè)公共的接口類dci::Unknown,該接口需要被領(lǐng)域?qū)ο髉ublic繼承。能夠從dci::Unknown被轉(zhuǎn)化到的目標(biāo)role需要用UNKNOWN_INTERFACE來定義,參數(shù)是類名以及一個(gè)32位的隨機(jī)數(shù)。這個(gè)隨機(jī)數(shù)需要程序員自行提供,保證全局不重復(fù)(可以寫一個(gè)腳本自動(dòng)產(chǎn)生不重復(fù)的隨機(jī)數(shù),同樣可以用腳本自動(dòng)校驗(yàn)代碼中已有的是否存在重復(fù),可以把校驗(yàn)?zāi)_本作為版本編譯檢查的一部分)。領(lǐng)域?qū)ο箢惱^承的所有由UNKNOWN_INTERFACE定義的role都需要在BEGIN_INTERFACE_TABLE()END_INTERFACE_TABLE()中由__HAS_INTERFACE顯示注冊(cè)一下(參考上面代碼中HumanObject的寫法)。最后,context持有領(lǐng)域?qū)ο蠊S返回的dci::Unknown指針,通過dci::unknown_cast將其轉(zhuǎn)化目標(biāo)role使用,至此這種機(jī)制和dynamic_cast的用法基本一致,在無法完成轉(zhuǎn)化的情況下會(huì)返回空指針,所以安全起見需要對(duì)返回的指針進(jìn)行校驗(yàn)。

上述提供的RTTI替代手段,雖然比直接使用RTTI略顯復(fù)雜,但是增加的手工編碼成本并不大,帶來的好處卻是明顯的。例如對(duì)嵌入式開發(fā),這種機(jī)制相比RTTI來說對(duì)程序員是可控的,可以選擇在僅需要該特性的范圍內(nèi)使用,避免無謂的內(nèi)存和性能消耗。

本文講解的C++的DCI編程框架,目前作為CUB的一個(gè)組件提供。CUB是一個(gè)面向嵌入式系統(tǒng)的C++基礎(chǔ)編程庫,我們?cè)趲讉€(gè)大型電信系統(tǒng)的重構(gòu)過程中大面積地使用了CUB庫和DCI架構(gòu),取得了非常好的效果!

作者:MagicBowen, Email:e.bowen.wang@icloud.com,轉(zhuǎn)載請(qǐng)注明作者信息,謝謝!

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

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

  • “Design is there to enable you to keep changing the softw...
    _張曉龍_閱讀 13,121評(píng)論 3 50
  • 序言 筆者在《軟件設(shè)計(jì)的演變過程》一文中,將通信系統(tǒng)軟件的DDD分層模型最終演進(jìn)為五層模型,即調(diào)度層(Schedu...
    _張曉龍_閱讀 2,740評(píng)論 2 16
  • 引言 在討論DDD分層架構(gòu)的模式之前,我們先一起回顧一下DDD和分層架構(gòu)的相關(guān)知識(shí)。 DDD DDD(Domain...
    _張曉龍_閱讀 161,882評(píng)論 15 193
  • 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)旨在軟件設(shè)計(jì)過程中提煉領(lǐng)域模型,以領(lǐng)域模型為核心改善業(yè)務(wù)專家和軟件開發(fā)者的溝通方式,對(duì)企業(yè)級(jí)...
    MagicBowen閱讀 6,020評(píng)論 0 29
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,637評(píng)論 18 399

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