I am new in Golang and I make an example for learning it but I am facing import cycle not allowed into my example so anyone know how to avoid this? Here is my code.
Bank,go
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")
}
}
Human.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)
}
In above code is work fine with
Bank.Transfer(&Sam, &gary, 5000)
But I think it should be Human use the Bank function so how can i re-write it into this?
Sam.Transfer(&gary, 5000)
I have tried to import Bank.go in Human.go but got the import cycle not allowed error. I am not sure is that my logical mistake or my bad code design but let see if someone can have a solution on this.
Updated Content below
After reading the message, i still don't understand how to implement Interface in this scenario. However, i did changed my code, please take a look to see if it is much better in code design for golang or it still the same? Thank you.
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)
}
Welcome to Golang and Stack Overflow!
This seems to be a general software engineering question on how to design the data structures and operations in your project and their dependencies.
As you've discovered, circular imports are bad. There are many ways of changing the design to decouple things. One is clear layers - for example, Bank
should probably depend on Human
but not the other way around. However, if you want to provide convenient functionality to transfer money from Human
to Human
, one thing you could do is define an interface that a Bank
object will implement.
For simplicity, however, I'd recommend strict layering. There's no real reason a Human
should depend on a Bank
. In the limit this can end up too cumbersome, since Human
s need more services (would you make a Human
depend on a Bus
to make it possible for Bus
es to move Human
s around?)
To answer the comment and the updated question, I'd keep it simple:
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)
}
This code uses loose coupling, as mentioned in my original answer. All the bank needs to know about a human is their ID (could be SSN or something calculated from the name, date of birth and other things) to identify them uniquely. Humans should hold banks (what if a person has accounts in multiple banks?). Banks shouldn't hold humans (what if accounts belong to multiple persons, corporations, virtual entities?) and so on. There's no need for interfaces here, and you can safely place each data type in their own package if you really need to.