为什么Go设计时,将变量分配给具有相同签名的任何接口时不标记为错误?

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

Why Go design not to mark as error when assign a variable to whatever interface that has same signature

问题

我刚开始学习Go语言,我的大部分背景来自Java和Ruby。

我想知道为什么Go语言设计者故意在实现接口时不指定接口类型。这样,如果有人意外地尝试将一个对象分配给与签名匹配但并不打算实现该接口的接口,这将导致一个错误,并且无法在编译时捕获。

你可以在这里尝试:https://play.golang.org/p/1N0kg7m4eE

package main

import "fmt"

type MathExpression interface {
    calculate() float64
}

type AreaCalculator interface {
    calculate() float64
}

type Square struct {
    width, height float64
}

//这个实现意图实现AreaCalculator接口
func (s Square) calculate() float64 {
    return s.width * s.height
}

func main() {
    //假设开发人员从某个地方获取了这个对象,但并不知道该对象是一个Square结构体
    mysteryStruct := Square{width: 4, height: 3}
    var areaCalculator AreaCalculator = mysteryStruct
    fmt.Println("某物的面积:", areaCalculator.calculate())
    
    var mathExpression MathExpression = mysteryStruct
    fmt.Println("这应该不起作用:", mathExpression.calculate())
}
英文:

I'm just start learning Go, most of my background came from Java, Ruby.

I just wonder that, from my example, why Go language designer intentionally design to not specify an interface type when implement an interface. So that if someone accidentally try to assign an object to an interface that match a signature but doesn't intend to implement that interface, that would lead to be a bug and cannot be catch at a compile time.

You can try at this: https://play.golang.org/p/1N0kg7m4eE

package main

import "fmt"

type MathExpression interface {
	calculate() float64
}

type AreaCalculator interface {
	calculate() float64
}

type Square struct {
	width, height float64
}

//This implementation intend to implement AreaCalculator interface
func (s Square) calculate() float64 {
	return s.width * s.height
}

func main() {
    //Suppose that a developer got this object from 
    //somewhere without a knowledge that object is a Square struct
	mysteryStruct := Square{width: 4, height: 3}
	var areaCalculator AreaCalculator = mysteryStruct
	fmt.Println("Area of something:", areaCalculator.calculate())
	
	var mathExpression MathExpression = mysteryStruct
	fmt.Println("This should not work:", mathExpression.calculate())
}

答案1

得分: 2

“隐式满足”的接口(即类型不需要显式声明它们实现了接口)的一个优点是可以事后创建接口。你可以看到几个类型有一个共同的方法,可能在不同的包中,然后决定编写一个函数,可以接受任何这些类型,通过调用一个新的接口来指定该方法。

此外,Go语言对接口的处理方式使得你可以在不修改原始包的情况下,编写一个对类型行为进行抽象的接口。我脑海中首先想到的例子是net/http包中的File接口:

type File interface {
    io.Closer
    io.Reader
    io.Seeker
    Readdir(count int) ([]os.FileInfo, error)
    Stat() (os.FileInfo, error)
}

它表示一个可以由http.FileServer提供的文件。通常情况下,它是一个os.File,但它也可以是任何满足该接口的类型。例如,我记得有人实现了一个从zip归档中提供文件的实现。

由于net/http包是在标准库中定义的,可能可以显式声明os.File实现了http.File,但这将使os包依赖于net/http包。这是一个循环依赖,因为net/http依赖于os。

在基于继承的语言中,试图这样做的人可能会放弃使用接口,并使http.FileServer要求所有文件都是os.File的子类。但这将很麻烦,因为他们不需要os.File的任何实现,他们只是继承它以满足类型系统。

原帖中的示例之所以有效,是因为方法名选择不当。不清楚Square的calculate方法应该返回什么。它的面积?周长?对角线?如果该方法被命名为CalculateArea,这将是一个命名习惯的名称,用于AreaCalculator接口中的单个方法,那么AreaCalculator和MathExpression就不会混淆。

英文:

One advantage of "implicitly satisfied" interfaces (ones where types don't need to explicitly declare that they implement them) is that interfaces can be created after the fact. You can see several types that have a method in common, perhaps in different packages, and decide to write a function that can accept any of them by calling for a new interface that specifies that method.

Also, Go's approach to interfaces lets you write an abstraction of the behavior of a type in an existing package, without modifying the original package. The first example that comes to my mind is the File interface in the net/http package:

type File interface {
	io.Closer
	io.Reader
	io.Seeker
	Readdir(count int) ([]os.FileInfo, error)
	Stat() (os.FileInfo, error)
}

It represents a file that can by served by an http.FileServer. Normally it is an os.File, but it could be anything that fulfills the interface. For example, I think someone has made an implementation that serves files out of a zip archive.

Since the net/http package is defined in the standard library, it might have been possible to explicitly declare that os.File implements http.File—but it would have made the os package depend on the net/http package. This is a circular dependency, because net/http depends on os.

In an inheritance-based language, someone who was trying to do this would probably just give up on using an interface, and make http.FileServer require that all files be subclasses of os.File. But this would be a pain, because they wouldn't need any of the implementation of an os.File; they would just be inheriting from it to satisfy the type system.

The example in the OP works because of a poorly-chosen method name. It is not at all clear what a Square's calculate method should return. Its area? Its perimeter? Its diagonal? If the method were named CalculateArea, which would be the idiomatic name for the single method in an interface named AreaCalculator, it would not be possible to confuse an AreaCalculator and a MathExpression.

答案2

得分: 1

这种方法的一个好处是依赖反转。在具有静态类型系统的典型语言中,你在实现和接口之间存在源代码级别的依赖关系。这意味着你不能将它们分开部署。而使用隐式接口,你没有源代码级别的依赖关系,实现模块可以在不包含接口的模块的情况下进行部署/开发/构建。这为你提供了通常保留给动态类型系统的灵活性。

英文:

One benefit of this approach is inversion of dependencies. In typical languages with static type systems, you have source level dependency from implementation to interface. Which means you can't deploy them separately. With implicit interfaces you have no source level dependency and implementation module can be deployed/developed/build without module containing interface. This gives you flexibility usually reserved for dynamic type systems.

答案3

得分: 1

Go使用隐式接口,这意味着如果类型具有接口所需的所有函数,则该类型实现了该接口。请注意,您不必在代码中声明类型实现接口(就像在Java中一样)。

这种语言设计有好有坏的后果:

  • 类型可能会意外地实现接口(在你的例子中发生了这种情况)。实际上,这种情况很少发生,所以我认为这不是一个大问题。
  • 在代码中没有接口声明,您不知道您的类型实现了哪些接口。这个问题可以通过使用好的IDE来解决。
  • 您可以轻松创建适配器和类似的设计模式。
  • 您可以轻松创建模拟对象。
  • 代码更易读(在Go中,类型声明非常简单)。
英文:

Go uses implicit interfaces, it means that type implements interface if it has all functions required by interface. Note that you don't have to declare in code that type implements interface (as in Java).

Such language design have good and bad consequences:

  • type can implement interface by accident (this happened in your example). Actually it happens very rarely so IMO it is not a big problem.
  • without interface declaration in code you don't know which interfaces your type implements. This problem can be solved by using good IDEs.
  • you can easily create adapters and similar design patterns
  • you can easily create mocks
  • more readable code (type declaration is very simple in go)

huangapple
  • 本文由 发表于 2016年2月21日 00:15:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/35525714.html
匿名

发表评论

匿名网友

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

确定