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)行了分層使得軟件更容易被理解。
- Context是盡可能薄的一層。Context往往被實(shí)現(xiàn)得無狀態(tài),只是找到合適的role,讓role交互起來完成業(yè)務(wù)邏輯即可。但是簡(jiǎn)單并不代表不重要,顯示化context層正是為人去理解軟件業(yè)務(wù)流程提供切入點(diǎn)和主線。
- Data層描述系統(tǒng)有哪些領(lǐng)域概念及其之間的關(guān)系,該層專注于領(lǐng)域?qū)ο蠛椭g關(guān)系的確立,讓程序員站在對(duì)象的角度思考系統(tǒng),從而讓系統(tǒng)是什么更容易被理解。
- 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è)類),Worker用USE_ROLE聲明它的實(shí)現(xiàn)需要依賴于另一個(gè)role:Energy,Worker在它的實(shí)現(xiàn)中調(diào)用ROLE(Energy)訪問它提供的接口方法。Energy是一個(gè)抽象類,有兩個(gè)子類HumanEnergy和ChargeEnergy分別對(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組合了Worker和HumanEnergy,而Robot組合了Worker和ChargeEnergy。最后在領(lǐng)域?qū)ο蟮念悆?nèi)還需要完成role之間的關(guān)系交織。由于Worker中聲明了USE_ROLE(Energy),所以當(dāng)Human和Robot繼承了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_ROLE和IMPL_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_cast將actor指針轉(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)景下,使用Actor和Worker的context的實(shí)現(xiàn)文件中僅需要包含Actor和Worker的頭文件即可,不會(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)注明作者信息,謝謝!

