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

huangapple go评论161阅读模式
英文:

Why can't Go method Receiving Types be interfaces?

问题

Go文档中的方法声明中可以看到:

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

有人能给我一些关于为什么会这样的见解吗?还有其他(静态类型的)语言允许这样吗?我真的想在接口上定义方法,这样我就可以将任何给定接口类型的实例视为另一个接口类型。例如(从Wikipedia上的模板方法模式文章中借用的例子)如果以下代码是有效的:

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"的实例作为"Game",而无需进行任何转换:

var newGame Game
newGame = NewMonopolyGame() // 实现了GameImplementation
newGame.PlayOneGame(2)

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

英文:

From the Go documentation on method declarations:

> The receiver type must be of the form T or *T where T is a type name. T is called the receiver base type or just base type. The base type must not be a pointer or interface type and must be declared in the same package as the method.

Can anyone give me some insight on why this might be? Are there any other (statically typed) languages that would allow this? I really want to define methods on an interface so I can treat any instance of a given interface type as another. For example (stealing the example from the Wikipedia article on the Template Method Pattern) if the following was valid:

<!-- language: lang-go -->

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()
}

I could use any instance implementing "GameImplementation" as a "Game" without any conversion:

<!-- language: lang-go -->

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

UPDATE: the purpose of this was to try and achieve all the benefits of abstract base classes without all the coupling that goes with an explicit hierarchy. If I wanted to define a new behaviour PlayBestOfThreeGames, abstract base classes would require me to change the base class itself - whereas here I just define one more method on top of the GameImplementation interface

答案1

得分: 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和任何特定的游戏实现可能位于不同的包中。

这里有一些关于golang-nuts的讨论

英文:

It's probably for the same reason you can't define methods on interfaces in Java.

An interface is meant to be a description of a part of, or the whole of, the external interface for a set of objects and not how they implement the underlying behavior. In Java you would probably use an abstract class if you need parts of the behavior to be pre-defined but I think the only way to do that in Go is to use functions rather than methods.

I believe that for your example the more Go idiomatic code would be something like this:

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()
}

Where PlayOneGame and any specific game implementation are probably living in different packages.

Here is some discussion on golang-nuts

答案2

得分: 1

回答你的问题,是否有其他静态类型语言允许这样做:是的,大多数语言都允许。任何具有多重继承的语言都允许类具有抽象和具体方法的任意组合。此外,可以看到Scala的特质(traits),它们类似于Java的接口,但可以具有具体方法。Scala还有结构类型,这实际上就是Go语言接口的全部内容。

英文:

In answer to your question of whether there are other statically typed languages that allow this: yes, most. Any language with multiple inheritance allows classes to have arbitrary mixes of abstract and concrete methods. Also, see Scala's traits, which are like Java's interfaces but can have concrete methods. Scala also has structural types, which are really all that Go's interfaces are.

答案3

得分: 1

你所描述的接口实际上可能在其他地方被称为抽象类 - 也就是说,一个类中有一些方法被定义了,但不是全部,必须通过继承才能实例化。

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

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

这使得在Go语言中无法使用泛型等模式,Rob Pike在一次会议上表示,如果有人能提出一个优雅的实现和一个有说服力的用例,这可能会在将来发生改变。但这还有待观察。

英文:

What you're describing as in Interface is really what might elsewhere be referred to as an abstract class -- that is, a class with some methods defined but not all, which must be subclassed in order to be instantiated.

However, Go doesn't have any concept of a class hierarchy -- the whole type structure is flat. Each method on a class is defined for that class specifically, not on any parent class or subclass or interface. This was a conscious design decision, not an omission.

In Go, an Interface is therefore not a component of a type hierarchy (as there is no such thing). Instead, it is simply an ad-hoc specification of the set of methods which must be implemented for a given purpose. That's all. They're a stand-in for dynamic typing whereby you can declare ahead of time which functions on a given type you'll be using -- then any variable who's type satisfies those requirements can be used.

This makes it impossible to use patterns like Generics with Go, and Rob Pike has said at a conference that this might be changed in the future if someone can come with a an elegant implementation and a compelling use case. But that remains yet to be seen.

答案4

得分: 1

首先,重要的是要注意类型在接口上的实现是隐式的 - 也就是说,接口是“鸭子类型”。只要某个类型提供了接口所需的方法,就可以将该类型赋值给接口类型的变量,而无需原始类型的任何协作。这与Java或C#不同,Java或C#中实现接口的类必须声明其意图来实现接口,除了实际提供方法之外。

Go语言也非常反对“远程操作”。例如,尽管方法与类型分开声明,但在不同包中声明方法与其接收器类型是非法的。你不能随意向os.File添加方法。

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

这会带来脆弱性的问题 - 更改接口所需的方法的签名,会导致一堆其他方法出现或消失。在它们消失的情况下,不明显它们“本应该”来自哪里。如果类型必须声明它们的意图来实现接口,那么违反合同将引发错误(而“意外”实现接口不会产生任何效果),但是当接口隐式满足时,情况就会变得更加棘手。

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

基本上,如果接口可以提供方法,那将非常酷 - 作为可组合的行为单元的角色非常酷,并且与Go语言的组合优于继承的理念非常契合 - 但实际上这样做对于Go语言来说过于复杂且具有太多的远程操作。

英文:

First, it's important to notice that types implement interfaces implicitly — that is, interfaces are "duck types". Any type that provides the methods required by the interface is assignable to a variable of the interface type, without any cooperation from the original type. This is different from, say, Java or C# where a class that implements an interface has to declare its intention to implement the interface, in addition to actually providing the methods.

Go also has a pretty strong tendency against "action at a distance". For example, even though methods are declared separately from types, it's illegal to declare a method in a different package from its receiver type. You can't just go adding methods to os.File.

If interfaces could provide methods (making them traits/roles) then any type that implemented an interface would gain a bunch of new methods out of nowhere. Someone reading the code and seeing those methods used probably have a hard time figuring out where they came from.

There's a problem with fragility — change the signature of a method that's required by an interface, and a bunch of other methods appear or disappear. In the case where they disappeared, it's not obvious where they "would have" come from. If types had to declare their intention to implement an interface then breaking the contract would prompt an error (and "accidentally" implementing an interface does nothing), but when interfaces are satisfied implicitly things are trickier.

Worse, there could be name conflicts — an interface provides a method with the same name as a method provided by a type that implements that interface, or two interfaces both provide a method with the same name, and some type happens to implement both of those interfaces. Resolving that conflict is the kind of complication that Go really likes to avoid, and in a lot of cases there is no satisfying resolution.

Basically, it would be really cool if interfaces could provide methods — roles as composable units of behavior are cool, and mesh well with Go's composition-over-inheritance philosophy — but actually doing it would be too complicated and too action-at-a-distance-y for Go to contemplate.

huangapple
  • 本文由 发表于 2011年6月4日 21:54:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/6237113.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定