动静兼备

动静兼备

动静兼备 #

接口的静态特性体现在接口类型变量具有静态类型,比如var err error中变量 err 的静态类型为 error。拥有静态类型,那就意味着编译器会在编译阶段对所有接口类型变量的赋值操作进行类型检查,编译器会检查右值的类型是否实现了该接口方法集合中的所有方法。如果不满足,就会报错:

var err error = 1
// cannot use 1 (type int) as type error in assignment:
//int does not implement error (missing Error method)

接口的动态特性,就体现在接口类型变量在运行时还存储了右值的真实类型信息,这个右值的真实类型被称为接口类型变量的动态类型。你看一下下面示例代码:

var err error
err = errors.New("error1")
fmt.Printf("<2022-08-07 日 10:53>\n", err)  // *errors.errorString

这个示例通过 errros.New 构造了一个错误值,赋值给了 error 接口类型变量 err,并通过 fmt.Printf 函数输出接口类型变量 err 的动态类型为 *errors.errorString。

“动静皆备”好处:

  1. 接口类型变量在程序运行时可以被赋值为不同的动态类型变量,每次赋值后,接口类型变量中存储的动态类型信息都会发生变化,这让 Go 语言可以像动态语言(比如 Python)那样拥有使用Duck Typing(鸭子类型)的灵活性。

比如下面的例子:

type QuackableAnimal interface {
    Quack()
}
type Duck struct{}
func (Duck) Quack() {
    println("duck quack!")
}
type Dog struct{}
func (Dog) Quack() {
    println("dog quack!")
}
type Bird struct{}
func (Bird) Quack() {
    println("bird quack!")
}
func AnimalQuackInForest(a QuackableAnimal) {
    a.Quack()
}
func main() {
    animals := []QuackableAnimal{new(Duck), new(Dog), new(Bird)}
    for _, animal := range animals {
        AnimalQuackInForest(animal)
    }
}

我们用接口类型 QuackableAnimal 来代表具有“会叫”这一特征的动物,而 Duck、Bird 和 Dog 类型各自都具有这样的特征,于是我们可以将这三个类型的变量赋值给 QuackableAnimal 接口类型变量 a。每次赋值,变量 a 中存储的动态类型信息都不同,Quack 方法的执行结果将根据变量 a 中存储的动态类型信息而定。

这里的 Duck、Bird、Dog 都是“鸭子类型”,但它们之间并没有什么联系,之所以能作为右值赋值给 QuackableAnimal 类型变量,只是因为他们表现出了 QuackableAnimal 所要求的特征罢了。

  1. 与动态语言不同的是,Go 接口还可以保证“动态特性”使用时的安全性。比如,编译器在编译期就可以捕捉到将 int 类型变量传给 QuackableAnimal 接口类型变量这样的明显错误,决不会让这样的错误遗漏到运行时才被发现。

From #

29|接口:为什么nil接口不等于nil?