Добро пожаловать на Голанг и переполнение стека!
Похоже, это общий вопрос разработки программного обеспечения о том, как проектировать структуры данных и операции в вашем проекте и их зависимости.
Как вы обнаружили, круговой импорт плох. Есть много способов изменить дизайн, чтобы отделить вещи. Один из них - прозрачные слои - например, Bank
, вероятно, должен зависеть от Human
, но не наоборот. Однако, если вы хотите предоставить удобную функциональность для перевода денег с Human
на Human
, вы могли бы определить интерфейс, который будет реализован объектом Bank
.
Однако для простоты я бы порекомендовал строгое наложение. Нет реальной причины, по которой Human
должен зависеть от Bank
. В пределе это может оказаться слишком громоздким, поскольку Human
s нужно больше услуг (вы бы поставили зависимость Human
от Bus
, чтобы Bus
могли перемещать Human
с?)
Чтобы ответить на комментарий и обновленный вопрос, я бы сказал, что это просто:
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)
}
Этот код использует слабую связь, как упоминалось в моем первоначальном ответе. Все, что банку нужно знать о человеке, - это его удостоверение личности (может быть SSN или что-то, вычисленное по имени, дате рождения и другим вещам), чтобы идентифицировать их однозначно. Люди должны держать банки (что, если у человека есть счета в нескольких банках?). Банки не должны содержать людей (что делать, если счета принадлежат нескольким лицам, корпорациям, виртуальным объектам?) И так далее. Здесь нет необходимости в интерфейсах, и вы можете безопасно поместить каждый тип данных в свой собственный пакет, если вам действительно нужно.