小類大對象
軟件設(shè)計(jì)本質(zhì)是解決分與合的問題。我們先將系統(tǒng)分解成很多單一職責(zé)的小類,然后利用“依賴注入“(Golang)或多重繼承(C++)的手段再將它們組合成對象盲厌。所以,類應(yīng)該是小的祸泪,對象應(yīng)該是大的吗浩。
類作為一種模塊化手段,遵循高內(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ù)的事物背后是否是一頭大象飒硅。
注:小類大對象和DCI(Data, Context, Interaction)
殊途同歸砂缩。
轉(zhuǎn)賬問題
假設(shè)我們有下面的一個(gè)轉(zhuǎn)賬場景:
-
zhangsan123
帳戶中存了1000
元 -
lisi456
帳戶中存了200
元 -
zhangsan123
給lisi456
轉(zhuǎn)賬300
元
我們先將系統(tǒng)分解成單一職責(zé)的類:
上圖中的類對應(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ā)回官。