Syntax 为什么可以';t Go方法接收类型是否为接口?

Syntax 为什么可以';t Go方法接收类型是否为接口?,syntax,interface,methods,go,Syntax,Interface,Methods,Go,从: 接收方类型必须为T或*T格式,其中T是类型名称。T被称为接收器基本类型或仅仅是基本类型基类型不能是指针或接口类型,必须在与方法相同的包中声明 有谁能告诉我为什么会这样?还有其他(静态类型的)语言允许这样做吗?我真的想在接口上定义方法,这样我就可以将给定接口类型的任何实例视为另一个实例。例如(从中窃取示例),如果以下内容有效: 类型游戏界面{ PlayOneGame(playersCount int) } 类型Game实现接口{ 初始化名称() MakePlay(玩家智力) EndOfGam

从:

接收方类型必须为T或*T格式,其中T是类型名称。T被称为接收器基本类型或仅仅是基本类型基类型不能是指针或接口类型,必须在与方法相同的包中声明

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

类型游戏界面{
PlayOneGame(playersCount int)
}
类型Game实现接口{
初始化名称()
MakePlay(玩家智力)
EndOfGame()bool
PrintWinner()
}
func(游戏*游戏实现)PlayOneGame(PlayerCount int){
game.InitializeGame()
对于j:=0;!game.EndOfGame();j=(j+1)%playersCount{
game.MakePlay(j)
}
game.PrintWinner()
}
我可以使用任何实现“GameImplementation”的实例作为“游戏”,而无需任何转换:

var新游戏
newGame=NewMonopolyGame()//实现游戏实现
新游戏,PlayOneGame(2)

更新:其目的是尝试实现抽象基类的所有好处,而不需要显式层次结构的所有耦合。如果我想定义一个新的游戏行为PlayBestOfThreeGames,抽象基类将要求我更改基类本身——而在这里,我只是在GameImplementation接口上定义了另一个方法,这可能与您不能在Java接口上定义方法的原因相同

接口是对一组对象的外部接口的一部分或全部的描述,而不是它们如何实现底层行为的描述。在Java中,如果需要预定义部分行为,您可能会使用抽象类,但我认为在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和任何特定的游戏实现可能生活在不同的包中


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

您在接口中描述的实际上是在其他地方可能被称为抽象类的东西——也就是说,一个定义了一些方法但不是所有方法的类,必须将其子类化才能实例化

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

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


这使得在Go中使用泛型这样的模式是不可能的,Rob Pike在一次会议上说,如果有人能够提供一个优雅的实现和一个引人注目的用例,这种情况在将来可能会改变。但这一点还有待观察。

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

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

如果接口能够提供方法(使它们成为可能),那么任何实现接口的类型都会从中获得大量新方法。阅读代码并看到使用的这些方法的人可能很难弄清楚它们来自何处

脆弱性存在一个问题——更改接口所需的方法的签名,其他一些方法就会出现或消失。如果它们消失了,它们“会在哪里”就不明显了如果类型必须声明其实现接口的意图,那么违反契约将提示错误(并且“意外地”实现接口不会产生任何作用),但是当接口得到隐式满足时,事情就变得更棘手了

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

基本上,如果接口能够提供方法,那就太酷了——作为行为的可组合单元的角色很酷,并且能够很好地与Go的组合相融合-