为什么 Go 方法接收类型不能是接口?
来自 有关方法声明的 Go 文档:
接收器类型必须采用 T 或 *T 形式,其中 T 是类型名称。 T 称为接收者基本类型或简称为基本类型。 基类型不能是指针或接口类型,并且必须在与方法相同的包中声明。
谁能给我一些关于为什么会这样的见解?是否有任何其他(静态类型)语言允许这样做?我真的很想在接口上定义方法,这样我就可以将给定接口类型的任何实例视为另一个接口类型。例如(从 关于模板方法模式的维基百科文章中窃取示例)如果以下内容是有效的:
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 接口之上定义一个方法
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:
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:
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这可能与 Java 中无法在接口上定义方法的原因相同。
接口是对一组对象的外部接口的一部分或全部的描述,而不是它们如何实现底层行为。在 Java 中,如果您需要预定义部分行为,您可能会使用抽象类,但我认为在 Go 中做到这一点的唯一方法是使用函数而不是方法。
我相信对于你的例子来说,更惯用的代码会是这样的:
其中 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:
Where PlayOneGame and any specific game implementation are probably living in different packages.
Here is some discussion on golang-nuts
回答您是否有其他静态类型语言允许这样做的问题:是的,大多数。任何具有多重继承的语言都允许类具有抽象方法和具体方法的任意组合。另外,请参阅 Scala 的特征,它类似于 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.
您在 Interface 中描述的实际上是在其他地方可能被称为抽象类的内容 - 也就是说,定义了一些但不是全部方法的类,必须对其进行子类化才能实例化。
然而,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.
首先,重要的是要注意类型隐式实现接口——也就是说,接口是“鸭子类型”。任何提供接口所需方法的类型都可以分配给接口类型的变量,而无需与原始类型进行任何合作。这与 Java 或 C# 不同,在 Java 或 C# 中,除了实际提供方法之外,实现接口的类还必须声明其实现接口的意图。
围棋也有相当强烈的反对“远距离行动”的倾向。例如,即使方法与类型分开声明,但在与其接收者类型不同的包中声明方法是非法的。您不能只是向 os.File 添加方法。
如果接口可以提供方法(使它们traits/roles),那么任何实现的类型一个接口会突然获得一堆新方法。阅读代码并查看所使用的这些方法的人可能很难弄清楚它们来自哪里。
脆弱性存在一个问题 - 更改接口所需的方法的签名,并且一堆其他方法会出现或消失。在他们失踪的情况下,他们“本来”来自哪里并不清楚。如果类型必须声明其实现接口的意图,那么违反约定将提示错误(并且“意外”实现接口不会执行任何操作),但是当隐式满足接口时,事情会变得更加棘手。
更糟糕的是,可能会出现名称冲突——一个接口提供的方法与实现该接口的类型提供的方法同名,或者两个接口都提供同名的方法,而某个类型恰好同时实现了这两个接口接口。解决这种冲突是 Go 真正希望避免的复杂情况,并且在很多情况下没有令人满意的解决方案。
基本上,如果接口能够提供方法,那就真的很酷了——角色作为可组合的行为单元很酷,并且与 Go 的组合优于继承的哲学很好地契合——但实际上这样做会太复杂,而且太单一了。 distance-y 供 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.