【问题标题】:When should we utilize method receiver / argument during Go interface implementation?我们应该在 Go 接口实现期间何时使用方法接收器/参数?
【发布时间】:2021-10-13 14:56:50
【问题描述】:

所以据我了解,为了在 Go 中实现多态性,我们希望在接口实现期间利用类型接收器并使用它的值。

在这种情况下,我们应该在接口实现期间何时使用方法参数而不是接收器?从我的角度来看,制作具有严格/大量签名的界面会使界面变得不那么有用。

假设我有这个结构、接口和实现

type BankAccount struct {
    UserId uint64, 
    AccountName string, 
    BankName string
}
type IBankAccount interface { 
    CheckBankAccount()
}
func (b *BankAccount) CheckBankAccount() { 
// do something that require b.UserId, b.AccountName and b.BankName
}
    
//we call it like this 
b:= BankAccount{UserId: 1, AccountName: "123", BankName: "some-bank"}
b.CheckBankAccount()

比这个好吗?

type BankAccount struct {}
type IBankAccount interface { 
    CheckBankAccount(userId uint64, accountName string, bankName string)
}

func (b *BankAccount) CheckBankAccount(userId uint64, accountName string, bankName string) { 
// do something that require userid accountname and bank name
}
  //calling
b:= BankAccount{}
b.CheckBankAccount(1, "123", "some-bank")

但看起来像第二个例子,接口实现更像普通函数(?)

另一方面,第一个示例似乎隐藏了很多细节,并要求未来的代码阅读器读取实现中使用的参数。这是抽象应该是什么还是我误解了这个概念?

【问题讨论】:

  • 第一个接口作为接口基本没用。
  • 这是否意味着我们应该平衡利用接收者和参数的价值?与第二个示例一样,但在结构中使用 BankName(例如)? @HymnsForDisco
  • 可能你误解了struct和interface的关系。接口的目的是通过一组方法描述一个完整的、有用的行为。这就是为什么第一个“几乎没用”的原因。唯一的方法不带参数,也不返回任何值。从实际的角度来看,在不获取或不产生任何数据的情况下,尚不清楚如何完整且有效地描述“回溯帐户”的概念。
  • 这可能是我的一个小问题,但这可能是一个“泡沫包装”的例子,其中具体的数据类型被不必要/过早的接口所掩盖,导致复杂的实现和抽象stackoverflow.com/a/67133656/11424673跨度>
  • 哦,抱歉,不返回值完全是我的坏事。忘记放了,因为它来自实际项目,所以必须删除一些东西。 CheckBankAccount 将返回包含有关银行帐户是否可用的帐户详细信息的结构。

标签: go interface


【解决方案1】:

这里似乎有几个概念混淆了。

接口不会有任何字段,但实现接口的类型会。对于实现接口的类型,方法实现使用底层类型的字段是非常好的(并且很常见)。

调整你的例子,常见的方法是这样的:

// IBankAccount is an interface different types can implement.
type IBankAccount interface { 
    CheckBankAccount()
}

// BankAccount is a concrete bank account type.
type BankAccount struct {
    UserId uint64, 
    AccountName string, 
    BankName string
}

// CheckBankAccount makes BankAccount implement the IBankAccount
// interface.
func (b *BankAccount) CheckBankAccount() { 
    // It's fine to use b's fields here!!
}

关键部分是界面如何使用。这是一个接受实现IBankAccount接口的参数的函数;编译器将允许您将任何类型传递给someFunc,只要此类型实现接口即可。然后someFunc 可以调用接口的方法。

func someFunc(b IBankAccount) {
  // ...
  b.CheckBankAccount()
  // ...
}

我们可以通过以下方式致电someFunc

b := BankAccount{...}
someFunc(b)

显然,您可以创建不同的类型,例如FancyAccount,有自己的字段,如果它实现了CheckBankAccount,它也实现了IBankInterface,因此可以传递给someFunc


接口方法的签名取决于您的特定应用程序。通常,接口尽量简单——方法少,方法参数少。查看一些标准库接口以获得灵感。

【讨论】:

  • 感谢您的详细回答。如果我猜对了,如果界面应该很简单,那么我认为界面更适合定义行为而不是定义整个功能?所以我们可以通过使用多个接口来分解它来定义 CheckBankAccount 的行为,而不是仅仅让 CheckBackAccount 实现具体的实现,对吗?
  • @sswastioyono18:这真的取决于具体情况,而且很难在摘要中讨论。您的示例是一个玩具,因此最好讨论一个真实世界的界面。
猜你喜欢
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-10
  • 1970-01-01
  • 1970-01-01
  • 2016-01-27
  • 1970-01-01
相关资源
最近更新 更多