Go语言中的隐式接口冲问题

发布时间 2023-09-13 08:59:21作者: 夜与萤火

Go语言中的隐式接口冲突 问题

Go中隐式接口,只要实现了某个接口就能当作那个接口使用,但是在不同接口中,可能有相同名字的方法,这时候就有可能会有接口冲突的问题。

隐式接口的好处之一就是松耦合,接口之间容易相互转换

比如有个自定义接口 MyError 接口,里面有个Error()方法

type  MyError  interface {
    	Error()	 string
}

目前这个 MyError 就与Go中内置的 error 接口冲突了,因为都只有一个 Error() 方法

type  error   interface {
    	Error()	 string
}

这样的话因为隐式接口的原因,这两个接口就是等价的,只要实现了 Error() 方法,那么既是 MyError类型也是 error 类型

如果要把这两个接口区分开的话,也就是让这两个接口不等价

那么可以选择MyError中加一个 唯一的空方法 来区分两个接口

type   MyError   interface {
    	Error()    string
   	        ProtoMyError() 
} 

以下是个人理解:

这里MyError 接口有两个方法,其中的 空方法 是为了区分 error 接口,当一个结构体实现了 MyError中的两个方法时,那么它既是MyError类型也是Error类型,但是

如果只实现了 Error() 方法时,那么它就只是 error类型,而不是MyError类型。

添加 唯一空方法 是Protobuf 中的 proto.Message 所使用的办法

// Message is implemented by generated protocol buffer messages.
type Message interface {
	Reset()
	String() string
	ProtoMessage()
}

生成的每个 Message 类型有个特殊的 ProtoMessage 空方法, 特别对应 proto.Message 接口.

极端的做法是随机生成一个 特别的 方法名, 比如用 UUID 做唯一名字.

但是, 公开的名字依然有被别人恶意覆盖的危险(实际中不大可能).

更严格的做法是将这个用于区别接口的方法名定义为私有的方法. 比如 testing.TB:

type  TB   interface  {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Fail()
	FailNow()
	Failed() bool
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
	Log(args ...interface{})
	Logf(format string, args ...interface{})
	Skip(args ...interface{})
	SkipNow()
	Skipf(format string, args ...interface{})
	Skipped() bool

	// A private method to prevent users implementing the
	// interface and so future additions to it will not
	// violate Go 1 compatibility.
	private()
}

private 不仅仅是私有方法, 而且必须是 testing 包内部定义的 private() 方法的类型才能匹配这个接口!

因此 testing.TB 接口是全局唯一的, 不会出现等价可互换的接口.

现在 testing.TB 保证了接口的唯一性, 但是如何在外部实现 这个接口呢(private()testing 包内部定义的)?

我们可以从 testing.TB 接口继承这个 private() 方法:

package main

import (
	"fmt"
	"testing"
)

type  TB  struct  {
	testing.TB
}

func (p *TB)   Fatal(args ...interface{})  {
	fmt.Println("TB.Fatal disabled!")
}

func  main()  {
	var tb testing.TB = new(TB)
	tb.Fatal("Hello, playground")
}

play 地址: http://play.golang.org/p/tFB0fLwq9q

上面的代码模拟了显式接口, 而且 testing.TB 接口永远不用担心有冲突的危险.

参考文章:?