为什么Go方法不能接收类型是接口?

时间:2011-06-04 13:54:18

标签: syntax interface methods go

来自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接口之上定义了另一个方法

4 个答案:

答案 0 :(得分:3)

可能出于同样的原因,您无法在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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

您在Interface中描述的内容实际上可能在其他地方被称为抽象类 - 也就是说,一个定义了一些方法的类但不是全部,必须进行子类化以便实例化。

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

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

这使得无法使用像Generics with Go这样的模式,Rob Pike在会议上表示如果有人能够提供优雅的实现和引人注目的用例,将来可能会改变。但这还有待观察。

答案 3 :(得分:0)

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

对于“远距离行动”,Go也有很强烈的倾向。例如,即使方法是与类型分开声明的,但是在与其接收器类型不同的包中声明方法也是非法的。您不能只是将方法添加到os.File

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

脆弱性存在问题 - 更改接口所需方法的签名,以及一堆其他方法出现或消失。在他们消失的情况下,他们“会有”来自哪里并不明显。如果类型必须声明他们打算实现一个接口,那么破坏合同会提示错误(并且“意外地”实现一个接口什么都不做),但是当接口被隐含地满足时,事情就会变得棘手。

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

基本上,如果接口可以提供方法,那将是非常酷的 - 作为可组合行为单元的角色很酷,并且与Go的组合 - 继承传统哲学很好地融合 - 但实际上这样做太复杂了,而且操作太多了-a-distance-y for Go to contemplate。