普通网友 2019-01-17 09:45
浏览 205
已采纳

设计Go程序以避免循环依赖

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)

}
  • 写回答

1条回答 默认 最新

  • dongle7637 2019-01-17 14:33
    关注

    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 Humans need more services (would you make a Human depend on a Bus to make it possible for Buses to move Humans 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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 matlab 用yalmip搭建模型,cplex求解,线性化处理的方法
  • ¥15 qt6.6.3 基于百度云的语音识别 不会改
  • ¥15 关于#目标检测#的问题:大概就是类似后台自动检测某下架商品的库存,在他监测到该商品上架并且可以购买的瞬间点击立即购买下单
  • ¥15 神经网络怎么把隐含层变量融合到损失函数中?
  • ¥15 lingo18勾选global solver求解使用的算法
  • ¥15 全部备份安卓app数据包括密码,可以复制到另一手机上运行
  • ¥20 测距传感器数据手册i2c
  • ¥15 RPA正常跑,cmd输入cookies跑不出来
  • ¥15 求帮我调试一下freefem代码
  • ¥15 matlab代码解决,怎么运行