Interface 是Go语言里很优秀的一个设计,它本身是仅仅是一个结构体,但是通过interface我们可以实现面向对象的很多特性,比如多态、动态联编等。interface 在Go 语言里使用起来的感觉,相比于其它语言也要优雅得多。
底层实现
Interface 的底层只是一个简单的由c语言实现的数据结构:
|
|
type 表示它的类型,data 里存储了它的具体信息。对于Go语言,任何数据类型都是可以由interface{}来表示的。当我们使用时可以通过反射把里面的信息取出来。
方法实现
我们先定义一个简单的接口,类似于Java 中的那样。
|
|
定义一个结构体 Dog ,直接定义Run 和 Roar 方法,即可实现接口。
|
|
声明一个Dog 类型并初始化。
|
|
这样实现一个接口的方法很简单,只需要在定义某一类型的时候,定义一个同名的方法即可。
多态
我们可以直接声明一个该接口的变量,再让这个变量指向实现其方法类型的实例的地址,这个变量就可以调用此实例的方法。
|
|
同样定义另外一个实现该接口的类型。
|
|
再让上面声明的接口变量指向这个类型的实例的地址,实际上调用方法的对象则变成后者。
|
|
Go语言里面向对象的特性通过interface 很轻易地体现了出来。本质上上述声明的Animal 变量a 是一个指针,它底层的内存结构包含两个字段:
- receiver,a可以指向任意变量,只需要这个变量的类型实现了该接口,此时receiver 存储该变量的地址,实际上此地址指向的其实是该变量的一个副本,因为Go 会在堆上申请一段空间用以存储变量的副本。如果该变量不大于一个字,则该变量直接存储在receiver 中。
- method table ptr,类似于C++ 里的虚函数表,当a 指向某实例变量后,该字段存储其方法的入口地址。
类型转换
一个空的 interface ,不包含任何方法,因此它能够存储任意类型的值。
|
|
我们可以借此实现一个函数,它的参数可以接收任意类型的值。这时,interface 的用法就比较像Java里的Object 对象。同样,我们使用这个参数前需要类型转换,在Java里可以通过运算符instanceof 来判断一个对象的实例是否为某一类型,并可以通过显示类型转换来使用这个实例。 在Go语言里,我们通过断言一样可以做到这些。先看看断言的用法:
|
|
格式为:变量名.(类型名)。返回两个值,第一个值为转换类型后的实例,第二个值为布尔变量,如果传入的参数为Cat 类型就返回 true, 否则返回 false 。 调用testFunc 时,在函数里判断其传入的参数是否为Cat 类型,如果是就打印出它的值。
也可以直接使用断言返回的第一个值。用法如c := animal.(Cat),直接忽略判断的结果。下面我们为testFunc 加入更多类型的判断,这时,通过结合switch的使用,使代码可读性更加友好。
|
|