Golang中小类大对象的一种实现

小类大对象

软件设计本质是解决分与合的问题。我们先将系统分解成很多单一职责的小类,然后利用“依赖注入“(Golang)或多重继承(C++)的手段再将它们组合成对象。所以,类应该是小的,对象应该是大的

盲人摸象.png

类作为一种模块化手段,遵循高内聚低耦合,让软件易于应对变化,可以将类看做是领域对象拥有的职责或扮演的角色;对象作为一种领域对象的的直接映射,解决了过多的类带来的可理解性问题,让领域可以指导设计,设计真正反映领域,领域对象需要真正意义上的生命周期管理。

上帝类是糟糕的,但上帝对象却恰恰是我们所期盼的。就拿大象来说,它很大,大到可以为外部提供多种功能的服务,而对于每种不同的服务需要者,它就扮演不同的角色。对于不同个体,需要的是更加具体的服务,而不是一头大象,因而他也并不关心为他服务的事物背后是否是一头大象。

大象组件.png

注:小类大对象和DCI(Data, Context, Interaction)殊途同归。

转账问题

假设我们有下面的一个转账场景:

  • zhangsan123帐户中存了1000
  • lisi456帐户中存了200
  • zhangsan123lisi456转账300

我们先将系统分解成单一职责的类:


transfer-money-classes.png

上图中的类对应于DCI中的Methodful Roles,接口对应于DCI中的Methodless Roles

小类的实现

accountIdInfo类的实现

注:Golang中小写字母开头的标识符仅具有包内可见性。

//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类的实现

//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接口的实现

//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类的实现

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类的实现

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")
}

大对象的实现

Account对象的实现

//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的实现

//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的实现

CreateAccount函数的实现

//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函数的实现

//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函数的实现

//main.go
package main

import "transfer-money/api"

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

运行结果

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

小结

本文以转账问题为例,给出了Golang中小类大对象的一种依赖注入实现,重点是Role的交织的姿势,希望对读者有一定的启发。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 引言 在讨论DDD分层架构的模式之前,我们先一起回顾一下DDD和分层架构的相关知识。 DDD DDD(Domain...
    _张晓龙_阅读 159,327评论 16 193
  • 你,从暮色中走来 不言,不语 一弯浅笑 醉倒一片黑夜 你,从迷雾中走来 不慌,不忙 一声轻叹 清...
    碾小玉阅读 243评论 0 1
  • 今天要写写舅舅家二表姐的故事。 二姐在家里排行老二,上面有大姐,下面有两个弟弟,一个妹妹。小时候没什么可说的,正常...
    麦子2008阅读 282评论 11 8
  • 曾经的梦想是什么?我想当一个战地记者,笔杠子就是我的正义之旗,高中时期的我看了水均益的书,心中充满了英雄主义...
    小猪窝阅读 231评论 0 0
  • 授米投融智库平台,致力在变化的时代,整合成熟互联网模式和金融互联网思维,汇聚和培育实业金融领域实战专家,利用先进的...
    授米阅读 212评论 0 0