Implement Domain Object in Golang

序言

筆者在《軟件設(shè)計(jì)的演變過程》一文中渠鸽,將通信系統(tǒng)軟件的DDD分層模型最終演進(jìn)為五層模型下硕,即調(diào)度層(Schedule)弄息、事務(wù)層(Transaction DSL)尔崔、環(huán)境層(Context)、領(lǐng)域?qū)?Domain)和基礎(chǔ)設(shè)施層(Infrastructure)呛伴,我們簡單回顧一下:

ddd-layer-with-dci-dsl.png
  1. 調(diào)度層:維護(hù)UE的狀態(tài)模型勃痴,只包括業(yè)務(wù)的本質(zhì)狀態(tài),將接收到的消息派發(fā)給事務(wù)層热康。
  2. 事務(wù)層:對(duì)應(yīng)一個(gè)業(yè)務(wù)流程沛申,比如UE Attach,將各個(gè)同步消息或異步消息的處理組合成一個(gè)事務(wù)姐军,當(dāng)事務(wù)失敗時(shí)铁材,進(jìn)行回滾尖淘。當(dāng)事務(wù)層收到調(diào)度層的消息后,委托環(huán)境層的Action進(jìn)行處理著觉。
  3. 環(huán)境層:以Action為單位村生,處理一條同步消息或異步消息,將Domain層的領(lǐng)域?qū)ο骳ast成合適的role饼丘,讓role交互起來完成業(yè)務(wù)邏輯趁桃。
  4. 領(lǐng)域?qū)樱翰粌H包括領(lǐng)域?qū)ο蠹捌渲g關(guān)系的建模,還包括對(duì)象的角色role的顯式建模肄鸽。
  5. 基礎(chǔ)實(shí)施層:為其他層提供通用的技術(shù)能力卫病,比如消息通信機(jī)制、對(duì)象持久化機(jī)制和通用的算法等典徘。

對(duì)于業(yè)務(wù)來說蟀苛,事務(wù)層和領(lǐng)域?qū)佣挤浅V匾9P者在《Golang事務(wù)模型》一文中重點(diǎn)討論了事務(wù)層烂斋,本文主要闡述領(lǐng)域?qū)拥膶?shí)現(xiàn)技術(shù)屹逛,將通過一個(gè)案例逐步展開。

本文使用的案例源自MagicBowen的一篇熱文《DCI in C++》汛骂,并做了一些修改罕模,目的是將Golang版領(lǐng)域?qū)ο蟮闹饕獙?shí)現(xiàn)技術(shù)盡可能流暢的呈現(xiàn)給讀者。

領(lǐng)域?qū)ο蟮膶?shí)現(xiàn)

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

領(lǐng)域模型

通過對(duì)場(chǎng)景進(jìn)行分析,我們根據(jù)組合式設(shè)計(jì)的基本思想得到一個(gè)領(lǐng)域模型:

human-robot.png

物理設(shè)計(jì)

從領(lǐng)域模型中可以看出廷蓉,角色Worker既可以組合在領(lǐng)域?qū)ο驢uman中全封,又可以組合在領(lǐng)域?qū)ο驲obot中,可見領(lǐng)域?qū)ο蠛徒巧莾蓚€(gè)不同的變化方向桃犬,于是domain的子目錄結(jié)構(gòu)為:

object-role-dir.png

role的實(shí)現(xiàn)

Energy

Energy是一個(gè)抽象role刹悴,在Golang中是一個(gè)interface。它包含兩個(gè)方法:一個(gè)是消耗能量Consume攒暇,另一個(gè)是能量是否耗盡IsExhausted土匀。

Energy的代碼比較簡單,如下所示:

package role

type Energy interface {
    Consume()
    IsExhausted() bool
}

HumanEnergy

HumanEnergy是一個(gè)具體role形用,在Golang中是一個(gè)struct就轧。它既有獲取能量的吃飯方法Eat证杭,又實(shí)現(xiàn)了接口Energy的所有方法。對(duì)于HumanEnergy來說钓丰,Eat一次獲取的所有能量在Consume 10次后就完全耗盡躯砰。

HumanEnergy的代碼如下所示:

package role

type HumanEnergy struct {
    isHungry bool
    consumeTimes int
}

const MAX_CONSUME_TIMES = 10

func (h *HumanEnergy) Eat() {
    h.consumeTimes = 0
    h.isHungry = false
}

func (h *HumanEnergy) Consume() {
    h.consumeTimes++
    if h.consumeTimes >= MAX_CONSUME_TIMES {
        h.isHungry = true
    }
}

func (h *HumanEnergy) IsExhausted() bool {
    return h.isHungry
}

RobotEnergy

RobotEnergy是一個(gè)具體role,在Golang中是一個(gè)struct携丁。它既有獲取能量的充電方法Charge,又實(shí)現(xiàn)了接口Energy的所有方法兰怠。對(duì)于RobotEnergy來說梦鉴,Charge一次獲取的所有能量在Consume 100次后就完全耗盡。

RobotEnergy的代碼如下所示:

package role

type RobotEnergy struct {
    percent int
}

const (
    FULL_PERCENT = 100
    CONSUME_PERCENT = 1
)

func (r *RobotEnergy) Charge() {
    r.percent = FULL_PERCENT
}

func (r *RobotEnergy) Consume() {
    if r.percent > 0 {
        r.percent -= CONSUME_PERCENT
    }
}

func (r *RobotEnergy) IsExhausted() bool {
    return r.percent == 0
}

Worker

Worker是一名工人揭保,人和機(jī)器人在工作時(shí)都是一名Worker肥橙,工作的流程是一樣的,但是區(qū)別在于依賴的能量消耗和獲取方式不同秸侣。對(duì)于代碼實(shí)現(xiàn)來說Worker僅依賴于另一個(gè)角色Energy存筏,只有在Worker的實(shí)例化階段才需要考慮注入Energy的依賴。
Worker是一個(gè)具體role味榛,在Golang中是一個(gè)struct椭坚。它既有生產(chǎn)產(chǎn)品的方法Produce,又有獲取已生產(chǎn)的產(chǎn)品數(shù)的方法GetProduceNum搏色。

Worker的代碼如下所示:

package role

type Worker struct {
    produceNum int
    Energy Energy
}

func (w *Worker) Produce() {
    if w.Energy.IsExhausted() {
        return
    }
    w.produceNum++
    w.Energy.Consume()
}

func (w *Worker) GetProduceNum() int {
    return w.produceNum
}

領(lǐng)域?qū)ο蟮膶?shí)現(xiàn)

該案例中有兩個(gè)領(lǐng)域?qū)ο笊凭ィ粋€(gè)是Human,另一個(gè)是Robot频轿。我們知道垂涯,在C++中通過多重繼承來完成領(lǐng)域?qū)ο蠛推渲С值膔ole之間的關(guān)系綁定,同時(shí)在多重繼承樹內(nèi)通過關(guān)系交織來完成role之間的依賴關(guān)系描述航邢。這種方式在C++中比采用傳統(tǒng)的依賴注入的方式更加簡單高效耕赘,所以在Golang中我們盡量通過模擬C++中的多重繼承來實(shí)現(xiàn)領(lǐng)域?qū)ο螅皇莾H僅靠簡陋的委托膳殷。

在Golang中可以通過匿名組合來模擬C++中的多重繼承操骡,role之間的依賴注入不再是注入具體role,而是將領(lǐng)域?qū)ο笾苯幼⑷牖嘀梢员苊猱a(chǎn)生很多小對(duì)象当娱。
在我們的案例中,角色Worker依賴于抽象角色Energy考榨,所以在實(shí)例化Worker時(shí)跨细,要么注入HumanEnergy,要么注入RobotEnergy河质,這就需要產(chǎn)生具體角色的對(duì)象(小對(duì)象)冀惭。領(lǐng)域?qū)ο驢uman在工作時(shí)是一名Worker震叙,消耗的是通過吃飯獲取的能量,所以Human通過HumanEnergy和Worker匿名組合而成散休。Golang通過了匿名組合實(shí)現(xiàn)了繼承媒楼,那么就相當(dāng)于Human多重繼承了HumanEnergy和Worker,即Human也實(shí)現(xiàn)了Energy接口戚丸,那么給Energy注入Human就等同于注入了HumanEnergy划址,同時(shí)避免了小對(duì)象HumanEnergy的創(chuàng)建。同理限府,Robot通過RobotEnergy和Worker匿名組合而成夺颤,Worker中的Energy注入的是Robot。

Human的實(shí)現(xiàn)

Human對(duì)象中有一個(gè)方法inject用于role的依賴注入胁勺,Human對(duì)象的創(chuàng)建通過工廠函數(shù)CreateHuman實(shí)現(xiàn)世澜。

Human的代碼如下所示:

package object

import(
    "domain/role"
)

type Human struct {
    role.HumanEnergy
    role.Worker
}

func (h *Human) inject() {
    h.Energy = h
}

func CreateHuman() *Human {
    h := &Human{}
    h.inject()
    return h
}

Robot的實(shí)現(xiàn)

同理,Robot對(duì)象中有一個(gè)方法inject用于role的依賴注入署穗,Robot對(duì)象的創(chuàng)建通過工廠函數(shù)CreateRobot實(shí)現(xiàn)寥裂。

Robot的代碼如下所示:

package object

import(
    "domain/role"
)

type Robot struct {
    role.RobotEnergy
    role.Worker
}

func (r *Robot) inject() {
    r.Energy = r
}

func CreateRobot() *Robot {
    r := &Robot{}
    r.inject()
    return r
}

領(lǐng)域?qū)ο蟮氖褂?/h2>

在Context層中,對(duì)于任一個(gè)Action案疲,都有明確的場(chǎng)景使得領(lǐng)域?qū)ο骳ast成該場(chǎng)景的role封恰,并通過role的交互完成Action的行為。在Golang中對(duì)于匿名組合的struct络拌,默認(rèn)的變量名就是該struct的名字俭驮。當(dāng)我們?cè)L問該struct的方法時(shí),既可以直接訪問(略去默認(rèn)的變量名)春贸,又可以通過默認(rèn)的變量名訪問混萝。我們推薦通過默認(rèn)的變量名訪問,從而將role顯式化表達(dá)出來萍恕。由此可見逸嘀,在Golang中領(lǐng)域?qū)ο骳ast成role的方法非常簡單,我們僅僅借助這個(gè)默認(rèn)變量的特性就可直接訪問role允粤。

HumanProduceInOneCycleAction

對(duì)于Human來說崭倘,一個(gè)生產(chǎn)周期就是HumanEnergy角色Eat一次獲取的能量被角色Worker生產(chǎn)產(chǎn)品消耗的過程。HumanProduceInOneCycleAction是針對(duì)這個(gè)過程的一個(gè)Action类垫,代碼實(shí)現(xiàn)簡單模擬如下:

package context

import (
    "fmt"
    "domain/object"
)

func HumanProduceInOneCycleAction() {
    human := object.CreateHuman()
    human.HumanEnergy.Eat()

    for {
        human.Worker.Produce()
        if human.HumanEnergy.IsExhausted() {
            break
        }
    }
    fmt.Printf("human produce %v products in one cycle\n", human.Worker.GetProduceNum())

}

打印如下:

human produce 10 products in one cycle

符合預(yù)期司光!

RobotProduceInOneCycleAction

對(duì)于Robot來說,一個(gè)生產(chǎn)周期就是RobotEnergy角色Charge一次獲取的能量被角色Worker生產(chǎn)產(chǎn)品消耗的過程悉患。RobotProduceInOneCycleAction是針對(duì)這個(gè)過程的一個(gè)Action残家,代碼實(shí)現(xiàn)簡單模擬如下:

package context

import (
    "fmt"
    "domain/object"
)

func RobotProduceInOneCycleAction() {
    robot := object.CreateRobot()
    robot.RobotEnergy.Charge()

    for {
        robot.Worker.Produce()
        if robot.RobotEnergy.IsExhausted() {
            break
        }
    }
    fmt.Printf("robot produce %v products in one cycle\n", robot.Worker.GetProduceNum())

}

打印如下:

robot produce 100 products in one cycle

符合預(yù)期!

小結(jié)

本文通過一個(gè)案例闡述了Golang中領(lǐng)域?qū)ο蟮膶?shí)現(xiàn)要點(diǎn)售躁,我們歸納如下:

  1. 類是一種模塊化的手段坞淮,遵循高內(nèi)聚低耦合茴晋,讓軟件易于應(yīng)對(duì)變化,對(duì)應(yīng)role回窘;對(duì)象作為一種領(lǐng)域?qū)ο蟮闹苯佑成渑瞪茫鉀Q了過多的類帶來的可理解性問題,領(lǐng)域?qū)ο笥蓃ole組合而成啡直。
  2. 領(lǐng)域?qū)ο蠛徒巧莾蓚€(gè)不同的變化方向烁涌,我們?cè)谧鑫锢碓O(shè)計(jì)時(shí)應(yīng)該是兩個(gè)并列的目錄。
  3. 通過匿名組合實(shí)現(xiàn)多重繼承酒觅。
  4. role的依賴注入單位是領(lǐng)域?qū)ο笈胗瘢皇蔷唧wrole。
  5. 使用領(lǐng)域?qū)ο髸r(shí)阐滩,不要直接訪問role的方法,而是先cast成role再訪問方法县忌。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掂榔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子症杏,更是在濱河造成了極大的恐慌装获,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厉颤,死亡現(xiàn)場(chǎng)離奇詭異穴豫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逼友,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門精肃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帜乞,你說我怎么就攤上這事司抱。” “怎么了黎烈?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵习柠,是天一觀的道長。 經(jīng)常有香客問我照棋,道長资溃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任烈炭,我火速辦了婚禮溶锭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梳庆。我一直安慰自己暖途,他們只是感情好卑惜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驻售,像睡著了一般露久。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欺栗,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天毫痕,我揣著相機(jī)與錄音,去河邊找鬼迟几。 笑死消请,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的类腮。 我是一名探鬼主播臊泰,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蚜枢!你這毒婦竟也來了缸逃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤厂抽,失蹤者是張志新(化名)和其女友劉穎需频,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筷凤,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昭殉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了藐守。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挪丢。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吗伤,靈堂內(nèi)的尸體忽然破棺而出吃靠,到底是詐尸還是另有隱情,我是刑警寧澤足淆,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布巢块,位于F島的核電站,受9級(jí)特大地震影響巧号,放射性物質(zhì)發(fā)生泄漏族奢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一丹鸿、第九天 我趴在偏房一處隱蔽的房頂上張望越走。 院中可真熱鬧,春花似錦、人聲如沸廊敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骡澈。三九已至锅纺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肋殴,已是汗流浹背囤锉。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留护锤,地道東北人官地。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像烙懦,于是被迫代替她去往敵國和親驱入。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • DCI[https://en.wikipedia.org/wiki/Data,_context_and_inter...
    MagicBowen閱讀 8,552評(píng)論 5 31
  • 引言 在討論DDD分層架構(gòu)的模式之前氯析,我們先一起回顧一下DDD和分層架構(gòu)的相關(guān)知識(shí)沧侥。 DDD DDD(Domain...
    _張曉龍_閱讀 160,360評(píng)論 16 193
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評(píng)論 25 707
  • 夢(mèng)想的白云 詩/柳六風(fēng) 在一個(gè)陽光明媚的早上, 我把夢(mèng)想的白云 種在棉花地里魄鸦。 好多棉花啊癣朗! 小草啊拾因, 那不是棉花...
    新觀點(diǎn)讀書閱讀 327評(píng)論 1 1
  • 有關(guān)手機(jī)到底能不能整晚充電的問題,貌似已經(jīng)存在了很久炉旷,網(wǎng)上也有各種各樣的解答签孔。而最近因?yàn)殡姵乇ǖ男侣勵(lì)^條也時(shí)有發(fā)...
    草莓CEO閱讀 544評(píng)論 0 1