【问题标题】:Why can't Go method Receiving Types be interfaces?为什么 Go 方法接收类型不能是接口?
【发布时间】:2011-09-08 09:21:24
【问题描述】:

来自Go documentation on method declarations

接收器类型必须是 T 或 *T 形式,其中 T 是类型名称。 T 被称为接收器基本类型或只是基本类型。 基类型不能是指针或接口类型,并且必须与方法在同一个包中声明。

谁能告诉我为什么会这样?是否有任何其他(静态类型)语言允许这样做?我真的很想在接口上定义方法,这样我就可以将给定接口类型的任何实例视为另一个实例。例如(从Wikipedia article on the Template Method Pattern 中窃取示例)如果以下内容有效:

type Game interface {
    PlayOneGame(playersCount int)
}

type GameImplementation interface {
    InitializeGame()
    MakePlay(player int)
    EndOfGame() bool
    PrintWinner()
}

func (game *GameImplementation) PlayOneGame(playersCount int) {
    game.InitializeGame()
    for j := 0; !game.EndOfGame(); j = (j + 1) % playersCount {
        game.MakePlay(j)
    }
    game.PrintWinner()
}

我可以将任何实现“GameImplementation”的实例用作“游戏”而无需任何转换:

var newGame Game
newGame = NewMonopolyGame() // implements GameImplementation
newGame.PlayOneGame(2)

更新:这样做的目的是尝试实现抽象基类的所有好处,而无需显式层次结构的所有耦合。如果我想定义一个新行为 PlayBestOfThreeGames,抽象基类将要求我更改基类本身 - 而这里我只是在 GameImplementation 接口之上再定义一个方法

【问题讨论】:

    标签: syntax interface methods go


    【解决方案1】:

    这可能与您无法在 Java 中的接口上定义方法的原因相同。

    接口旨在描述一组对象的外部接口的一部分或全部,而不是它们如何实现底层行为。在 Java 中,如果您需要预定义部分行为,您可能会使用抽象类,但我认为在 Go 中做到这一点的唯一方法是使用函数而不是方法。

    我相信对于您的示例,更多的 Go 惯用代码将是这样的:

    type GameImplementation interface {
        InitializeGame()
        MakePlay(player int)
        EndOfGame() bool
        PrintWinner()
    }
    
    func PlayOneGame(game GameImplementation, playersCount int) {
        game.InitializeGame()
        for j := 0; !game.EndOfGame(); j = (j + 1) % playersCount {
            game.MakePlay(j)
        }
        game.PrintWinner()
    }
    

    PlayOneGame 和任何特定游戏实现可能存在于不同的包中。

    Here is some discussion on golang-nuts

    【讨论】:

    • 'game *GameImplementation' 不应作为指针传递,因为它是接口,而不是结构。
    • 已更正。在我发表这篇文章后不久,我刚刚发现了一些信息。我对这门语言还是很陌生,以前从未见过任何地方提到过这个事实。
    • 为链接干杯 - 看起来主要原因是为了让事情变得简单并保持快速编译。
    • 我不认为这是一个令人满意的答案——你说它“可能出于同样的原因,你不能在 Java 中的接口上定义方法”,但不要详细说明这个原因是什么.实际上,您不能在 Java 中定义接口上的方法,因为 Java 有一个非常缺乏想象力的 OO 系统。有很多语言确实允许类似的事情,结果(见我的回答)允许接口定义方法在Java中比Go更安全:)
    【解决方案2】:

    回答您是否有其他静态类型语言允许这样做的问题:是的,大多数。任何具有多重继承的语言都允许类具有抽象和具体方法的任意组合。此外,请参阅 Scala 的特征,它们类似于 Java 的接口,但可以有具体的方法。 Scala 也有结构类型,这就是 Go 的所有接口。

    【讨论】:

    • 感谢您的建议-我最初并不清楚,我正在寻找一种完全避免继承的机制,即允许我们在不改变 GameImplementation 的情况下实现上述目标
    【解决方案3】:

    您在 Interface 中描述的实际上是在其他地方可能被称为抽象类的东西——即,一个定义了一些方法但不是全部方法的类,必须对其进行子类化才能实例化。

    然而,Go 没有任何类层次结构的概念——整个类型结构是扁平的。类上的每个方法都是专门为该类定义的,而不是在任何父类或子类或接口上定义的。这是一个有意识的设计决定,而不是遗漏。

    因此,在 Go 中,接口不是类型层次结构的组件(因为没有这样的东西)。相反,它只是针对特定目的必须实现的一组方法的临时规范。就这样。它们是动态类型的替代品,您可以提前声明您将使用的给定类型上的哪些函数——然后可以使用任何类型满足这些要求的变量。

    这使得在 Go 中使用泛型之类的模式成为不可能,Rob Pike 在一次会议上表示,如果有人能够提供一个优雅的实现和一个引人注目的用例,这可能会在未来改变。但这还有待观察。

    【讨论】:

      【解决方案4】:

      首先,重要的是要注意类型实现接口隐式——也就是说,接口是“鸭子类型”。任何提供接口所需方法的类型都可以分配给接口类型的变量,而无需与原始类型进行任何合作。这与 Java 或 C# 不同,在 Java 或 C# 中,实现接口的类除了实际提供方法外,还必须声明其实现接口的意图。

      围棋也有相当强烈的反对“远距离行动”的倾向。例如,即使方法与类型分开声明,在与其接收者类型不同的包中声明方法也是非法的。您不能只向os.File 添加方法。

      如果接口可以提供方法(使它们成为traits/roles),那么任何实现接口的类型都会突然获得一堆新方法。阅读代码并看到这些方法使用的人可能很难弄清楚它们来自哪里。

      脆弱性存在一个问题——更改接口所需的方法的签名,然后一堆其他方法出现或消失。在他们消失的情况下,他们“会”从哪里来并不明显。如果类型必须声明它们实现接口的意图,那么违反约定会提示错误(并且“意外地”实现接口什么也不做),但是当接口被隐式满足时,事情就变得更棘手了。

      更糟糕的是,可能存在名称冲突——一个接口提供的方法与实现该接口的类型提供的方法同名,或者两个接口都提供同名的方法,而某些类型恰好实现这两个接口。解决这种冲突是 Go 真正喜欢避免的那种复杂情况,并且在很多情况下,没有令人满意的解决方案。

      基本上,如果接口可以提供方法,那将是非常酷的——作为可组合行为单元的角色很酷,并且与 Go 的组合优于继承的哲学很好地结合——但实际上这样做会太复杂而且太复杂-a-distance-y 去思考。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-09
        • 1970-01-01
        • 2016-11-05
        • 2019-12-23
        • 2011-01-03
        • 1970-01-01
        相关资源
        最近更新 更多