Golang中小類大對象的一種實(shí)現(xiàn)

小類大對象

軟件設(shè)計(jì)本質(zhì)是解決分與合的問題。我們先將系統(tǒng)分解成很多單一職責(zé)的小類,然后利用“依賴注入“(Golang)或多重繼承(C++)的手段再將它們組合成對象盲厌。所以,類應(yīng)該是小的祸泪,對象應(yīng)該是大的吗浩。

盲人摸象.png

類作為一種模塊化手段,遵循高內(nèi)聚低耦合没隘,讓軟件易于應(yīng)對變化懂扼,可以將類看做是領(lǐng)域?qū)ο髶碛械穆氊?zé)或扮演的角色;對象作為一種領(lǐng)域?qū)ο蟮牡闹苯佑成溆移眩鉀Q了過多的類帶來的可理解性問題阀湿,讓領(lǐng)域可以指導(dǎo)設(shè)計(jì),設(shè)計(jì)真正反映領(lǐng)域瑰妄,領(lǐng)域?qū)ο笮枰嬲饬x上的生命周期管理陷嘴。

上帝類是糟糕的,但上帝對象卻恰恰是我們所期盼的间坐。就拿大象來說灾挨,它很大,大到可以為外部提供多種功能的服務(wù)竹宋,而對于每種不同的服務(wù)需要者劳澄,它就扮演不同的角色。對于不同個(gè)體蜈七,需要的是更加具體的服務(wù)秒拔,而不是一頭大象,因而他也并不關(guān)心為他服務(wù)的事物背后是否是一頭大象飒硅。

大象組件.png

注:小類大對象和DCI(Data, Context, Interaction)殊途同歸砂缩。

轉(zhuǎn)賬問題

假設(shè)我們有下面的一個(gè)轉(zhuǎn)賬場景:

  • zhangsan123帳戶中存了1000
  • lisi456帳戶中存了200
  • zhangsan123lisi456轉(zhuǎn)賬300

我們先將系統(tǒng)分解成單一職責(zé)的類:


transfer-money-classes.png

上圖中的類對應(yīng)于DCI中的Methodful Roles,接口對應(yīng)于DCI中的Methodless Roles狡相。

小類的實(shí)現(xiàn)

accountIdInfo類的實(shí)現(xiàn)

注:Golang中小寫字母開頭的標(biāo)識符僅具有包內(nèi)可見性梯轻。

//account_id_info.go
package domain

type AccountId string

type accountIdInfo struct {
    id AccountId
}

func newAccountIdInfo(id AccountId) *accountIdInfo {
    return &accountIdInfo{id}
}

func (a *accountIdInfo) getAccountId() AccountId {
    return a.id
}

balance類的實(shí)現(xiàn)

//balance.go
package domain

type Amount uint

type balance struct {
    amount Amount
}

func newBalance(amount Amount) *balance {
    return &balance{amount:amount}
}

func (b *balance) increase(amount Amount) {
    b.amount += amount
}

func (b *balance) decrease(amount Amount) {
    b.amount -= amount
}

func (b *balance) get() Amount {
    return b.amount
}

message接口的實(shí)現(xiàn)

//message.go
package domain

import "fmt"

type message interface {
    sendTransferToMsg(to *accountIdInfo, amount Amount)
    sendTransferFromMsg(from *accountIdInfo, amount Amount)
}

const msgPhone = 0
type msgType int

func newMessage(msgType msgType) message {
    if msgType == msgPhone {
        return &phone{}
    }
    return nil
}

type phone struct {

}

func (p *phone) sendTransferToMsg(to *accountIdInfo, amount Amount) {
    fmt.Println("phone: ", "send ", amount, " money to ", to.getAccountId())
}

func (p *phone) sendTransferFromMsg(from *accountIdInfo, amount Amount) {
    fmt.Println("phone: ", "receive ", amount, " money from ", from.getAccountId())
}

MoneySource類的實(shí)現(xiàn)

MoneySource類依賴于accountIdInfo類食磕,balance類和message接口尽棕。

//money_source.go
package domain

import "fmt"

type MoneySource struct {
    accountIdInfo *accountIdInfo
    balance *balance
    message message
}

func newMoneySource(accountIdInfo *accountIdInfo, balance *balance, message message) *MoneySource {
    return &MoneySource{accountIdInfo:accountIdInfo, balance:balance, message:message}
}


func (m *MoneySource) TransferMoneyTo(to *MoneyDestination, amount Amount) {
    fmt.Println("start: ", m.accountIdInfo.getAccountId(), " has ", m.balance.get(), " money")
    if m.balance.get() < amount {
        panic("insufficient money!")
    }
    to.TransferMoneyFrom(m.accountIdInfo, amount)
    m.balance.decrease(amount)
    m.message.sendTransferToMsg(to.getAccountId(), amount)
    fmt.Println("end: ", m.accountIdInfo.getAccountId(), " has ", m.balance.get(), " money")
}

MoneyDestination類的實(shí)現(xiàn)

MoneyDestination類依賴于accountIdInfo類,balance類和message接口彬伦。

//money_destination.go
package domain

import "fmt"

type MoneyDestination struct {
    accountIdInfo *accountIdInfo
    balance *balance
    message message
}

func newMoneyDestination(accountIdInfo *accountIdInfo, balance *balance, message message) *MoneyDestination {
    return &MoneyDestination{accountIdInfo:accountIdInfo, balance:balance, message:message}
}

func (m *MoneyDestination) getAccountId() *accountIdInfo {
    return m.accountIdInfo
}

func (m *MoneyDestination) TransferMoneyFrom(from *accountIdInfo, amount Amount) {
    fmt.Println("start: ", m.accountIdInfo.getAccountId(), " has ", m.balance.get(), " money")
    m.balance.increase(amount)
    m.message.sendTransferFromMsg(from, amount)
    fmt.Println("end: ", m.accountIdInfo.getAccountId(), " has ", m.balance.get(), " money")
}

大對象的實(shí)現(xiàn)

Account對象的實(shí)現(xiàn)

//account.go
package domain

type Account struct {
    accountIdInfo *accountIdInfo
    balance *balance
    message message
    MoneySource *MoneySource
    MoneyDestination *MoneyDestination
}

func NewAccount(accountId AccountId, amount Amount) *Account {
    account := &Account{}
    account.accountIdInfo = newAccountIdInfo(accountId)
    account.balance = newBalance(amount)
    account.message = newMessage(msgPhone)
    account.MoneySource = newMoneySource(account.accountIdInfo, account.balance, account.message)
    account.MoneyDestination = newMoneyDestination(account.accountIdInfo, account.balance, account.message)
    return account
}

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

//account_repo.go
package domain

import "sync"

var inst *AccountRepo
var once sync.Once

type AccountRepo struct {
    accounts map[AccountId]*Account
    lock sync.RWMutex
}

func GetAccountRepo() *AccountRepo {
    once.Do(func() {
        inst = &AccountRepo{accounts: make(map[AccountId]*Account)}
    })
    return inst
}

func (a *AccountRepo) Add(account *Account) {
    a.lock.Lock()
    a.accounts[account.accountIdInfo.getAccountId()] = account
    a.lock.Unlock()
}

func (a *AccountRepo) Get(accountId AccountId) *Account {
    a.lock.RLock()
    account := a.accounts[accountId]
    a.lock.RUnlock()
    return account
}

func (a *AccountRepo) Remove(accountId AccountId) {
    a.lock.Lock()
    delete(a.accounts, accountId)
    a.lock.Unlock()
}

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

CreateAccount函數(shù)的實(shí)現(xiàn)

//create_account.go
package api

import "transfer-money/domain"

func CreateAccount(accountId domain.AccountId, amount domain.Amount) {
    account := domain.NewAccount(accountId, amount)
    repo := domain.GetAccountRepo()
    repo.Add(account)
}

TransferMoney函數(shù)的實(shí)現(xiàn)

//transfer_money.go
package api

import "transfer-money/domain"

func TransferMoney(from domain.AccountId, to domain.AccountId, amount domain.Amount) {
    repo := domain.GetAccountRepo()
    src := repo.Get(from)
    dst := repo.Get(to)
    src.MoneySource.TransferMoneyTo(dst.MoneyDestination, amount)
}

測試

main函數(shù)的實(shí)現(xiàn)

//main.go
package main

import "transfer-money/api"

func main() {
    api.CreateAccount("zhangsan123", 1000)
    api.CreateAccount("lisi456", 200)
    api.TransferMoney("zhangsan123", "lisi456", 300)
}

運(yùn)行結(jié)果

start:  zhangsan123  has  1000  money
start:  lisi456  has  200  money
phone:  receive  300  money from  zhangsan123
end:  lisi456  has  500  money
phone:  send  300  money to  lisi456
end:  zhangsan123  has  700  money

小結(jié)

本文以轉(zhuǎn)賬問題為例滔悉,給出了Golang中小類大對象的一種依賴注入實(shí)現(xiàn)伊诵,重點(diǎn)是Role的交織的姿勢,希望對讀者有一定的啟發(fā)回官。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曹宴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子歉提,更是在濱河造成了極大的恐慌笛坦,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苔巨,死亡現(xiàn)場離奇詭異版扩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)侄泽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門礁芦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悼尾,你說我怎么就攤上這事柿扣。” “怎么了闺魏?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵未状,是天一觀的道長。 經(jīng)常有香客問我析桥,道長娩践,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任烹骨,我火速辦了婚禮翻伺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沮焕。我一直安慰自己吨岭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布峦树。 她就那樣靜靜地躺著辣辫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魁巩。 梳的紋絲不亂的頭發(fā)上急灭,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天,我揣著相機(jī)與錄音谷遂,去河邊找鬼葬馋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畴嘶。 我是一名探鬼主播蛋逾,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窗悯!你這毒婦竟也來了区匣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤蒋院,失蹤者是張志新(化名)和其女友劉穎亏钩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欺旧,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铸屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了切端。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彻坛。...
    茶點(diǎn)故事閱讀 40,872評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖踏枣,靈堂內(nèi)的尸體忽然破棺而出昌屉,到底是詐尸還是另有隱情,我是刑警寧澤茵瀑,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布间驮,位于F島的核電站,受9級特大地震影響马昨,放射性物質(zhì)發(fā)生泄漏竞帽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一鸿捧、第九天 我趴在偏房一處隱蔽的房頂上張望屹篓。 院中可真熱鬧,春花似錦匙奴、人聲如沸堆巧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谍肤。三九已至,卻和暖如春哗伯,著一層夾襖步出監(jiān)牢的瞬間荒揣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工焊刹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留系任,地道東北人恳蹲。 一個(gè)月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像赋除,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子非凌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評論 2 361

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

  • 引言 在討論DDD分層架構(gòu)的模式之前举农,我們先一起回顧一下DDD和分層架構(gòu)的相關(guān)知識。 DDD DDD(Domain...
    _張曉龍_閱讀 160,699評論 15 193
  • 你敞嗡,從暮色中走來 不言颁糟,不語 一彎淺笑 醉倒一片黑夜 你硫朦,從迷霧中走來 不慌律秃,不忙 一聲輕嘆 清...
    碾小玉閱讀 256評論 0 1
  • 今天要寫寫舅舅家二表姐的故事。 二姐在家里排行老二精续,上面有大姐箕肃,下面有兩個(gè)弟弟婚脱,一個(gè)妹妹。小時(shí)候沒什么可說的勺像,正常...
    麥子2008閱讀 313評論 11 8
  • 曾經(jīng)的夢想是什么障贸?我想當(dāng)一個(gè)戰(zhàn)地記者,筆杠子就是我的正義之旗吟宦,高中時(shí)期的我看了水均益的書篮洁,心中充滿了英雄主義...
    小豬窩閱讀 239評論 0 0
  • 授米投融智庫平臺,致力在變化的時(shí)代殃姓,整合成熟互聯(lián)網(wǎng)模式和金融互聯(lián)網(wǎng)思維袁波,匯聚和培育實(shí)業(yè)金融領(lǐng)域?qū)崙?zhàn)專家,利用先進(jìn)的...
    授米閱讀 250評論 0 0