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

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

Golang中小类大对象的一种实现
文章图片
盲人摸象.png
【Golang中小类大对象的一种实现】类作为一种模块化手段,遵循高内聚低耦合,让软件易于应对变化,可以将类看做是领域对象拥有的职责或扮演的角色;对象作为一种领域对象的的直接映射,解决了过多的类带来的可理解性问题,让领域可以指导设计,设计真正反映领域,领域对象需要真正意义上的生命周期管理。
上帝类是糟糕的,但上帝对象却恰恰是我们所期盼的。就拿大象来说,它很大,大到可以为外部提供多种功能的服务,而对于每种不同的服务需要者,它就扮演不同的角色。对于不同个体,需要的是更加具体的服务,而不是一头大象,因而他也并不关心为他服务的事物背后是否是一头大象。

Golang中小类大对象的一种实现
文章图片
大象组件.png
注:小类大对象和DCI(Data, Context, Interaction)殊途同归。
转账问题 假设我们有下面的一个转账场景:

  • zhangsan123帐户中存了1000
  • lisi456帐户中存了200
  • zhangsan123lisi456转账300
我们先将系统分解成单一职责的类:

Golang中小类大对象的一种实现
文章图片
transfer-money-classes.png 上图中的类对应于DCI中的Methodful Roles,接口对应于DCI中的Methodless Roles
小类的实现
accountIdInfo类的实现 注:Golang中小写字母开头的标识符仅具有包内可见性。
//account_id_info.go package domaintype AccountId stringtype accountIdInfo struct { id AccountId }func newAccountIdInfo(id AccountId) *accountIdInfo { return &accountIdInfo{id} }func (a *accountIdInfo) getAccountId() AccountId { return a.id }

balance类的实现
//balance.go package domaintype Amount uinttype 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 domainimport "fmt"type message interface { sendTransferToMsg(to *accountIdInfo, amount Amount) sendTransferFromMsg(from *accountIdInfo, amount Amount) }const msgPhone = 0 type msgType intfunc 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 domainimport "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 domainimport "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 domaintype 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 domainimport "sync"var inst *AccountRepo var once sync.Oncetype 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 apiimport "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 apiimport "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 mainimport "transfer-money/api"func main() { api.CreateAccount("zhangsan123", 1000) api.CreateAccount("lisi456", 200) api.TransferMoney("zhangsan123", "lisi456", 300) }

运行结果
start:zhangsan123has1000money start:lisi456has200money phone:receive300money fromzhangsan123 end:lisi456has500money phone:send300money tolisi456 end:zhangsan123has700money

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

    推荐阅读