“領域驅動設計”答疑(四)

DDD in C

問題:代碼如何和領域模型保持一致廉油?C語言能清晰的表達領域模型嗎碰声?

第一個問題:代碼和模型保持一致跟啤,需要掌握用編程語言實現(xiàn)模型的“標準方式”,并具備寫出自注釋代碼的能力起趾。

第二個問題:能,但是要掌握模塊化C的設計和編碼方式警儒。


以下作為補充训裆,提供給感興趣的同學眶根。

我們知道模型是一種抽象,面向對象建模得到的模型結果是經(jīng)過選擇的對解決問題有價值的概念边琉、關系和約束的集合属百。

確實,面向對象編程語言可以方便的表達領域模型变姨。例如通過類可以表達領域概念族扰;通過interface表達接口和服務;通過private可以封裝屬性和行為定欧;通過構造和析構函數(shù)進行對象生命周期管理渔呵;通過繼承和引用可以表達泛化和組合關系等。因此選擇面向對象語言來實現(xiàn)領域模型是非常自然的砍鸠。

遺憾的是并不是所有實踐領域建模的同學對面向對象編程都是良好掌握的扩氢!見過不少同學能把所有單實例和多實例的領域概念都用單例表達;無論泛化還是組合關系都能用共有繼承來實現(xiàn)爷辱;無論引用對象的生命周期早或者晚于自己都用指針來表示... 录豺; 在這種實現(xiàn)下,即使做了領域建模饭弓,用了面向對象編程語言双饥,模型和代碼也沒有直接關系。

其實每種面向對象語言都有表達模型的“標準方式”弟断,很早的時候建模工具就已支持自動從模型生成指定編程語言的骨架代碼【た蓿現(xiàn)實中我們很少用這個功能,主要有以下原因:

1)大部分情況下夫嗓,模型圖都很難表達所有的實現(xiàn)語義(做不到迟螺、或者成本太高)。所以自動生成的代碼是不完備的舍咖,還需要人工修改代碼以補充缺失的實現(xiàn)細節(jié)矩父;

2)針對圖的編輯、重用排霉,重構窍株、版本管理,往往不如直接搞代碼來的高效攻柠;

3)每當模型變化后球订,從模型圖重新生成的代碼又要和已實現(xiàn)代碼進行merge,合并成本大瑰钮,效率低冒滩;

4)最后,對于像C/C++這樣比較底層或者復雜的語言來說浪谴,從模型到代碼的自動生成效果會更差开睡,不具備實用性因苹。

因此在現(xiàn)實的情況下,為了追求效率篇恒,程序員們絕大多數(shù)時候還是直接用代碼實現(xiàn)和演進模型扶檐。

但是手動實現(xiàn),不代表可以隨意實現(xiàn)胁艰!遵循一些從模型到代碼的最佳實踐款筑,或者叫做“實現(xiàn)模式”,會讓代碼更加清晰的表達模型腾么,甚至做到“望文生義”奈梳,降低代碼和模型的同步成本。

表達模型的實現(xiàn)模式哮翘,不同的編程語言會有區(qū)別颈嚼。以下是我總結的C++實現(xiàn)模型常用的實現(xiàn)模式。限于篇幅就不再展開了饭寺,對C++比較了解的同學應該都看的懂阻课。

C++面向模型的實現(xiàn)模式

那么用C語言能否很好的實現(xiàn)領域模型呢?

如前面所說艰匙,用編程語言表達模型限煞,需要為對應的編程語言建立起一套表達模型的“實現(xiàn)模式”。

雖然C語言被認為是一門過程化語言员凝,但并不是說C語言就沒有表達領域概念和關系的能力署驻。Robert C. Martin在《Clean Architecture》中甚至認為"C語言的限制其實更少,可以做出更靈活的設計選擇"健霹。

Anyway旺上,我們不去爭論編程語言的優(yōu)劣,我們來看看如何在C中表達領域概念和關系糖埋。

相比用C做過程化設計宣吱,現(xiàn)代化C編程更推崇使用模塊化C的設計方法。模塊化C要求用一個“.h”和一個“.c”文件組合實現(xiàn)一個概念(類似一個面向對象中的類)瞳别≌骱颍“.h”文件中包含該概念對應的結構體或者句柄,還有該概念支持的API聲明祟敛;而“.c”文件中包含API的函數(shù)實現(xiàn)疤坝,以及用static修飾的內(nèi)部共享狀態(tài)與私有函數(shù)實現(xiàn)。

如下代碼表達了Storage的概念馆铁,可以看到里面包含Storage的類型定義與API跑揉。所有API的第一個參數(shù)是Storage自身,加const表示該API是只讀的叼架,否則是可寫API畔裕。

#include "base/status.h"

typedef struct Storage
{
    int capacity;
    int type;
} Storage;

/* Read-only */
double storage_charge(const Storage* storage, int months);
int storage_level(const Storage* storage, int months);
/* Writable */
Status storage_promote(Storage* storage);

模塊化C中一般用結構體的包含關系或者指針引用表達模型中概念之間的組合關系衣撬。而模型中的泛化關系則需要用到C語言的“函數(shù)指針結構體”的設計技巧乖订,具體在編碼的時候還需要區(qū)分泛化關系背后的調用是無狀態(tài)還有有狀態(tài)的扮饶。

如下代碼示例如何通過action_create創(chuàng)建具有泛化關系的Action

#include "point.h"

typedef enum {
    ALERT_ACTION, CLEAN_ACTION, MAX_ACTION,
} ActionType;

/* Abstract Interface */
typedef struct Action {
    void* data;
    void (*exec)(void* data, const Point* point);
    void (*destroy)(void* data);
} Action;

/* Factory Function */
Action action_create(ActionType type, const Point* points, int numOfPoints);

介紹了如何用C語言表達概念以及概念間關系后,我們來看看生命周期管理乍构。

領域驅動設計強調對領域對象的生命周期進行顯示的建模和管理甜无。

在不考慮持久化的情況下,領域對象生命周期一般起始于構造函數(shù)哥遮,結束于析構函數(shù)岂丘。但是在C語言中結構體沒有顯示的構造和析構過程,所以生命周期管理一般對應于結構體內(nèi)存的分配與回收眠饮。

在嵌入式場景下奥帘,經(jīng)常使用全局變量按照業(yè)務規(guī)格在靜態(tài)內(nèi)存區(qū)預占內(nèi)存,這導致了程序員很容易把領域對象的生命周期管理和用于內(nèi)存預占的全局變量耦合在一起仪召。

全局變量是缺乏清晰的生命周期語義的寨蹋,它起始于進程初始化,銷毀于進程退出扔茅。而領域對象的生命周期的開始和結束是卻是有清晰的業(yè)務指示的已旧。如果代碼對領域對象的所有訪問都直接使用它對應的全局變量,就會導致領域對象生命周期管理和內(nèi)存管理混淆在一起召娜。再加上全局變量帶來的代碼耦合問題运褪,最終會導致代碼難以理解和維護。

對于這個問題玖瘸,我們可以借鑒領域驅動設計中提出的FactoryRepository的概念來承擔領域對象的生命周期管理職責秸讹,并對領域對象的內(nèi)存管理方式進行封裝,對外屏蔽領域對象的創(chuàng)建和存儲的技術細節(jié)雅倒。其它所有需要使用領域對象的代碼都應該通過Factory或者Repository獲得領域對象的句柄或者結構體指針璃诀,這樣核心的模型代碼就和內(nèi)存管理方式等基礎設施進行了解耦,也避免了和全局變量的耦合屯断,提升了代碼的可維護性與可理解性文虏。

更多關于模塊化C以及如何用C語言表達模型的方式,可以借鑒《C現(xiàn)代編程》和《嵌入式C設計模式》中的內(nèi)容殖演,希望隨后有機會能就這個話題更系統(tǒng)性的總結一下氧秘。

追求代碼本身就是模型的直接映射是領域驅動設計強調的一個核心。如果一個更好的解決方案只是存在于設計文檔中趴久,并沒有在代碼中實現(xiàn)猿挚,那么它是沒有產(chǎn)生實際價值的。因此陪竿,保持持續(xù)重構,當有更好的解決方案的時候膳算,就找機會重構進代碼里。只有被代碼真實反映的模型才是當前軟件中真正的模型弛作!


《“領域驅動設計”答疑(五)》

《“領域驅動設計”答疑(匯總)》

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末涕蜂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子映琳,更是在濱河造成了極大的恐慌机隙,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萨西,死亡現(xiàn)場離奇詭異有鹿,居然都是意外死亡,警方通過查閱死者的電腦和手機谎脯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門葱跋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人源梭,你說我怎么就攤上這事娱俺。” “怎么了咸产?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵矢否,是天一觀的道長。 經(jīng)常有香客問我脑溢,道長僵朗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任屑彻,我火速辦了婚禮验庙,結果婚禮上,老公的妹妹穿的比我還像新娘社牲。我一直安慰自己粪薛,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布搏恤。 她就那樣靜靜地躺著违寿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熟空。 梳的紋絲不亂的頭發(fā)上藤巢,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音息罗,去河邊找鬼掂咒。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绍刮。 我是一名探鬼主播温圆,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孩革!你這毒婦竟也來了岁歉?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嫉戚,失蹤者是張志新(化名)和其女友劉穎刨裆,沒想到半個月后澈圈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彬檀,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年瞬女,在試婚紗的時候發(fā)現(xiàn)自己被綠了窍帝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡诽偷,死狀恐怖坤学,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情报慕,我是刑警寧澤深浮,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站眠冈,受9級特大地震影響飞苇,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蜗顽,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一布卡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雇盖,春花似錦忿等、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至狸相,卻和暖如春薛匪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卷哩。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工蛋辈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓冷溶,卻偏偏與公主長得像渐白,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子逞频,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • DDD是什么 領域驅動設計(Domain Driven Design纯衍,DDD)是由 Eric Evans 最早提出...
    彥幀閱讀 2,245評論 0 4
  • TITLE: 編程語言亂燉 碼農(nóng)最大的煩惱——編程語言太多。不是我不學習苗胀,這世界變化快襟诸! 有時候還是蠻懷念十幾、二...
    碼園老農(nóng)閱讀 5,331評論 2 35
  • 冬天的東北小村基协,外面是寒風刺骨歌亲,屋子里家家都會使勁的燒木頭,從早到晚都不停下澜驮,維持著屋內(nèi)不均勻的溫度陷揪,下面炕上是...
    貝貝222閱讀 27,010評論 0 0
  • 今天陪阿姨去買衣服的時候看見了一條小雛菊的裙子,很漂亮杂穷。但是不適合我的身體悍缠,然后在阿姨和售貨員的慫恿之下,又不小心...
    碧聰Green閱讀 169評論 0 0
  • 最近幾天真的是熱死了耐量,悶熱悶熱的飞蚓,家里空調開一夜,到店里就開空調也不風涼廊蜒,主要店里空間太大了趴拧,一個水溫空調不頂事,...
    王飛媽媽閱讀 80評論 0 0