【问题标题】:How to avoid a golang function having different behaviors between calling the embedding and embedded types?如何避免 golang 函数在调用嵌入类型和嵌入类型之间具有不同的行为?
【发布时间】:2018-08-13 06:35:31
【问题描述】:

假设在第 3 方库中,我们有一个接口和一个实现该接口的结构。我们还假设有一个以 ParentInterface 作为参数的函数,它对不同的类型有不同的行为。

type ParentInterface interface {
    SomeMethod()
}

type ParentStruct struct {
    ...
}

func SomeFunction(p ParentInterface) {
    switch x := p.Type {
    case ParentStruct:
        return 1
    }
    return 0
}

在我们的代码中,我们想使用这个接口,但是我们的行为增强了,所以我们将它嵌入到我们自己的结构中。编译器实际上允许我们直接在我的结构上调用关于 ParentInterface 的函数:

type MyStruct struct {
    ParentInterface
}

parentStruct := ParentStruct{...}
myStruct := MyStruct{parentStruct}

parentStruct.SomeMethod()  // Compiler OK.
myStruct.SomeMethod()  // Compiler OK. Result is same. Great.

SomeFunction(parentStruct)  // Compiler OK. Result is 1.
SomeFunction(myStruct.ParentInterface)  // Compiler OK. Result is 1.
SomeFunction(myStruct)  // Compiler OK. Result is 0. (!)

最后一种情况不是问题吗?我不止一次遇到过这种错误。因为我很乐意在我的代码中使用MyStruct 作为ParentInterface 的别名(这就是我首先定义它的原因),所以很难记住我们不能直接在MyStruct 上调用SomeFunction (编译器说我们可以!)。

那么避免这种错误的最佳做法是什么?或者它实际上是编译器的一个缺陷,它应该禁止使用SomeFunction(myStruct),因为结果无论如何都是不可信的?

【问题讨论】:

  • 我不明白你在做什么。但是您描述的行为非常有意义,因为您正在检查 SomeFunction 中的基础类型。为了避免您看到的行为,可能只是不要这样做 - 而是依赖界面。但同样,我不知道你的目标,所以我真的不能说如何实现它。

标签: go methods struct interface embedding


【解决方案1】:

这里没有编译器错误,您体验到的结果是预期的。

您的 SomeFunction() 函数明确声明它希望根据传递的接口值的动态类型做不同的事情,这正是发生的事情。

我们首先引入了接口,因此我们不必关心实现它的动态类型。该接口为我们提供了对现有方法的保证,而这些是您应该依赖的唯一东西,您应该只调用这些方法,而不是做一些类型切换或断言功夫。

当然这是理想的世界,但你应该尽可能地坚持下去。

即使在某些情况下您无法将所有内容都放入接口中,如果您需要额外的功能,您可以再次键入 assert 另一个接口而不是具体类型。

一个典型的例子是编写一个http.Handler,您可以在其中将响应编写器作为接口:http.ResponseWriter。它非常简约,但是传递的实际类型可以做更多的事情。要访问该“更多”,您可以使用其他类型断言来获取该额外接口,例如http.Pusherhttp.Flusher

在 Go 中,没有继承和多态。 Go 有利于组合。当您将一个类型嵌入到另一个类型(结构)中时,嵌入类型的method set 将成为嵌入器类型的一部分。这意味着嵌入类型实现的任何接口,嵌入器也将实现这些。并且调用这些实现接口的方法会将调用“转发”到嵌入类型,也就是说,这些方法调用的接收者将是嵌入的值。除非您通过提供您自己的实现来“覆盖”这些方法,并且接收器类型是嵌入器类型。但即使在这种情况下,虚拟路由也不会发生。这意味着如果嵌入类型具有方法A()B(),并且A() 的实现调用B(),如果您在嵌入器上提供自己的B(),则调用A()(属于嵌入类型)不会调用你的B(),而是调用嵌入类型的。

这不是要避免的事情(你无法避免),这是需要了解的事情(可以忍受的事情)。如果您知道这是如何工作的,那么您只需考虑到这一点并加以考虑。

因为我很乐意在我的代码中使用MyStruct 作为ParentInterface 的别名(这就是我首先定义它的原因)

您不应该使用嵌入来创建别名,这是对嵌入的滥用。在您自己的类型中嵌入类型不会是别名。如您所见,检查具体类型的现有方法的实现将“失败”(这意味着它们将找不到与其预期的具体类型相匹配的匹配项)。

除非您想“覆盖”某些方法或以这种方式实现某些接口,否则不应使用嵌入。只需使用原始类型。最简单,最干净。如果需要别名,Go 1.9 introduced the type alias feature,其语法为:

type NewType = ExistingType

在上述声明NewTypeExistingType 相同之后,它们将完全可以互换(因此具有相同的方法集)。但是要知道,这不会为语言添加任何新的“真实”特性,任何使用类型别名的可能性都可以在没有它们的情况下实现。主要是为了支持更简单、渐进的代码重构。

【讨论】:

  • 感谢您的详细解释。很有教育意义。我还有一个问题。显然,即使不是为了别名,结构仍然可能出于任何原因包含接口。那么在哪种情况下SomeFunction(myStruct) 有意义?这对编译器来说显然是一些额外的工作——它必须足够聪明,才能识别出myStruct 实际上与ParentInterface 的实例相似(如果不等于),所以它让SomeFunction() 去。如果这只是一种错误做法,那么这样做(对于编译器)有什么好处?
  • @DavidM SomeFunction() 需要一个实现 ParentInterface 的值。任何实现它的值都是有效的、可接受的,并在编译时进行检查。 mystruct 的类型实现了该接口,这是进行函数调用的唯一重要事项。调用时传递的值的具体类型无关紧要,唯一重要的是它实现了param的接口类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-19
  • 1970-01-01
相关资源
最近更新 更多