go - 设计 Go 程序以避免循环依赖
问题描述
我是 Golang 的新手,我举了一个例子来学习它,但我面临着不允许进入我的例子的导入周期,所以有人知道如何避免这种情况吗?这是我的代码。
银行,走
package Bank
import (
"../../class/human"
"fmt"
)
func Transfer(payer, receiver *human.Human, payment float64) {
if payer.Bank > payment {
payer.Bank -= payment
receiver.Bank += payment
} else {
fmt.Println("Bank balance not enough")
}
}
人类.go
package human
// import "../../func/Bank"
type Human struct {
Name string
Cash float64
Bank float64
}
func (h *Human) Transfer(Receiver Human, payment float64) {
}
Main.go
package main
import (
"./class/human"
"./func/Bank"
)
func main() {
gary := human.Human{"Gary", 2000.0, 40000.0}
Sam := human.Human{"Sam", 10000.0, 500000.0}
Bank.Transfer(&Sam, &gary, 5000)
}
在上面的代码中可以正常工作
Bank.Transfer(&Sam, &gary, 5000)
但我认为应该是人类使用银行功能所以我怎么能把它重写成这个呢?
Sam.Transfer(&gary, 5000)
我尝试在 Human.go 中导入 Bank.go,但出现导入周期不允许错误。我不确定是我的逻辑错误还是我糟糕的代码设计,但让我们看看是否有人可以解决这个问题。
更新内容如下
看完消息后,我还是不明白如何在这种情况下实现接口。但是,我确实更改了我的代码,请看看它在 golang 的代码设计中是否更好,还是还是一样?谢谢你。
package main
// Human.go
type Human struct {
Name string
Cash float64
Bank float64
}
// Bank.go
type Bank struct {
Acct *Human
}
func (b *Bank) Transfer(receiver *Human, payment float64) {
payer := b.Acct
payer.Bank -= payment
receiver.Bank += payment
}
// main.go
func main() {
gary := Human{"Gary", 2000.0, 40000.0}
Sam := Human{"Sam", 10000.0, 500000.0}
Sam_Account := Bank{&Sam}
Sam_Account.Transfer(&gary, 5000)
}
解决方案
欢迎使用 Golang 和 Stack Overflow!
这似乎是一个关于如何设计项目中的数据结构和操作及其依赖关系的一般软件工程问题。
正如您所发现的,循环导入是不好的。有很多方法可以改变设计来解耦。一个是清晰的层——例如,Bank
应该可能依赖于Human
但不是相反。但是,如果您想提供方便的功能将资金从 转移Human
到Human
,您可以做的一件事是定义一个Bank
对象将实现的接口。
然而,为了简单起见,我建议严格分层。没有真正的理由 aHuman
应该依赖于 a Bank
。在极限情况下,这可能会变得过于繁琐,因为Human
s 需要更多服务(您是否会Human
依赖 aBus
以使Bus
es 可以移动Human
s ?)
为了回答评论和更新的问题,我会保持简单:
package main
import (
"fmt"
"log"
)
type Human struct {
ID int64
Name string
}
type Account struct {
ID int64
// Note: floats aren't great for representing money as they can lose precision
// in some cases. Keeping this for consistency with original.
Cash float64
DaysSinceActive int64
}
type Bank struct {
Accounts map[int64]Account
}
// Not checking negatives, etc. Don't use this for real banking :-)
func (bank *Bank) Transfer(src int64, dest int64, sum float64) error {
srcAcct, ok := bank.Accounts[src]
if !ok {
return fmt.Errorf("source account %d not found", src)
}
destAcct, ok := bank.Accounts[dest]
if !ok {
return fmt.Errorf("destination account %d not found", dest)
}
// bank.Accounts[src] fetches a copy of the struct, so we have to assign it
// back after modifying it.
srcAcct.Cash -= sum
bank.Accounts[src] = srcAcct
destAcct.Cash += sum
bank.Accounts[dest] = destAcct
return nil
}
func main() {
gary := Human{19928, "Gary"}
sam := Human{99555, "Sam"}
bank := Bank{Accounts: map[int64]Account{}}
bank.Accounts[gary.ID] = Account{gary.ID, 250.0, 10}
bank.Accounts[sam.ID] = Account{sam.ID, 175.0, 5}
fmt.Println("before transfer", bank)
if err := bank.Transfer(gary.ID, sam.ID, 25.0); err != nil {
log.Fatal(err)
}
fmt.Println("after transfer", bank)
}
如我原来的答案所述,此代码使用松散耦合。银行只需要知道一个人的 ID(可能是 SSN 或根据姓名、出生日期和其他信息计算出来的东西)来唯一地识别他们。人类应该持有银行(如果一个人在多家银行拥有账户怎么办?)。银行不应该持有人(如果账户属于多个人、公司、虚拟实体怎么办?)等等。这里不需要接口,如果你真的需要,你可以安全地将每种数据类型放在它们自己的包中。
推荐阅读
- php - 多次包含时如何避免错误
- java - 总是返回 null 的方法
- python-3.x - 如何使用 Flask 向 Google Dialogflow 提交查询
- docker - docker swarm 服务可在 swarm 主机上访问,但不能在 LAN IP 地址上访问
- java - 从不同的方法调用相同的 API
- sql - SQL 组计数
- python - 如何通过字符串值和匹配行中的整数过滤熊猫数据框?
- java - 在java中创建一个打印子arrayList项的方法
- angular - 通过属性对象将组件传递给另一个组件(用于 ng-bootstrap 模态)
- tabulator - 如何使用制表符在输入框占位符中显示和更新当前页码