学习记录(Go语言高级编程)
对象和接口之间太灵活了,导致我们需要人为地限制这种无意之间的适配。常见的做法是定义一 个含特殊方法来区分接口。比如 runtime 包中的 Error 接口就定义了一个特有 的 RuntimeError 方法,用于避免其它类型无意中适配了该接口:
type runtime.Error interface {
error
RuntimeError()
}
在protobuf中, Message 接口也采用了类似的方法,也定义了一个特有的 ProtoMessage ,用于 避免其它类型无意中适配了该接口:
type proto.Message interface {
Reset()
String() string
ProtoMessage()
}
不过这种做法只是君子协定,如果有人刻意伪造一个 proto.Message 接口也是很容易的。再严格一 点的做法是给接口定义一个私有方法。只有满足了这个私有方法的对象才可能满足这个接口,而私有方法的名字是包含包的绝对路径名的,因此只能在包内部实现这个私有方法才能满足这个接口。测试包中 的 testing.TB 接口就是采用类似的技术:
type testing.TB interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
...
// A private method to prevent users implementing the
// interface and so future additions to it will not
// violate Go 1 compatibility.
private()
}
不过这种通过私有方法禁止外部对象实现接口的做法也是有代价的:首先是这个接口只能包内部使用, 外部包正常情况下是无法直接创建满足该接口对象的;其次,这种防护措施也不是绝对的,恶意的用户 依然可以绕过这种保护机制。
在前面的方法一节中我们讲到,通过在结构体中嵌入匿名类型成员,可以继承匿名类型的方法。其实这 个被嵌入的匿名成员不一定是普通类型,也可以是接口类型。我们可以通过嵌入匿名 的 testing.TB 接口来伪造私有的 private 方法,因为接口方法是延迟绑定,编译 时 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")
}
我们在自己的 TB 结构体类型中重新实现了 Fatal 方法,然后通过将对象隐式转换 为 testing.TB 接口类型(因为内嵌了匿名的 testing.TB 对象,因此是满足 testing.TB 接口 的),然后通过 testing.TB 接口来调用我们自己的 Fatal 方法
这种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接 口指定的规范,真正的实现在运行的时候才被注入。比如,我们可以模拟实现一个gRPC的插件:
type grpcPlugin struct {
*generator.Generator
}
func (p *grpcPlugin) Name() string {
return "grpc"
}
func (p *grpcPlugin) Init(g *generator.Generator) {
p.Generator = g
}
func (p *grpcPlugin) GenerateImports(file *generator.FileDescriptor) {
if len(file.Service) == 0 {
return
}
p.P(`import "google.golang.org/grpc"`)
// ...
}
构造的 grpcPlugin 类型对象必须满足 generate.Plugin 接口 (在”github.com/golang/protobuf/protoc-gen-go/generator”包中)
type Plugin interface {
Name() string
Init(g *Generator)
Generate(file *FileDescriptor)
GenerateImports(file *FileDescriptor)
}
generate.Plugin 接口对应的 grpcPlugin 类型的 GenerateImports 方法中使用 的 p.P(…) 函数却是通过 Init 函数注入的 generator.Generator 对象实现。这里 的 generator.Generator 对应一个具体类型,但是如果 generator.Generator 是接口类型的话 我们甚至可以传入直接的实现。
Go语言通过几种简单特性的组合,就轻易就实现了鸭子面向对象和虚拟继承等高级特性,真的是不可思议