Go之interface的具体使用
浅显地了解了一下 go,发现 go 语法的设计非常简洁,易于理解。正应了 go 语言之父 rob pike 说的那句“less is more”—— 大道至简。
下面就具体的语法特性说说我自己的体会。
interface
概览
与通常以类型层次与继承为根基的面向对象设计(oop)语言(如c++、java)不同,go 的核心思想就是组合(composition)。go 进一步解耦了对象与操作,实现了真正的鸭子类型(duck typing):一个对象如果能嘎嘎叫那就能当做鸭子,而不是像 c++ 或 java 那样需要类型系统去保证:一个对象先得是只鸭子,然后才能嘎嘎叫。
type duck interface { quack() } type animal struct { name string } func (animal animal) quack() { fmt.println(animal.name, ": quack! quack! like a duck!") } func main() { unknownanimal := animal{name: "unknown"} var equivalent duck equivalent = unknownanimal equivalent.quack() }
运行上面的代码输出:
unknown : quack! quack! like a duck!
下面用 java 语言来实现:
interface duck { void quack(); } class someanimal implements duck { string name; public someanimal(string name) { this.name = name; } public void quack() { system.out.println(name + ": quack! quack! i am a duck!"); } } public class test { public static void main(string []args){ someanimal unknownanimal = new someanimal("unknown"); duck equivalent = unknownanimal; equivalent.quack(); } }
两相比较就能看出:go 将对象与对其的操作(方法或函数)解耦得更彻底。go 并不需要一个对象通过类型系统来保证实现了某个接口(is a),而只需要这个对象实现了某个接口的方法即可(like a),而且类型声明与方法声明或实现也是松耦合的形式。如果稍微转换一下方法的实现方式:
func (animal animal) quack() { fmt.println(animal.name, ": quack! quack! like a duck!") }
为:
func quack(animal animal) { fmt.println(animal.name, ": quack! quack! like a duck!") }
是不是就和普通方法并无二致了?
在深入浅出 cocoa 之消息一文中我曾分析过 objective c 的消息调用过程:
bird * abird = [[bird alloc] init]; [abird fly];
中对 fly 的调用,编译器通过插入一些代码,将之转换为对方法具体实现 imp 的调用,这个 imp 是通过在 bird 的类结构中的方法链表中查找名称为 fly 的选择子 sel 对应的具体方法实现找到的,编译器会将消息调用转换为对消息函数 objc_msgsend的调用:
objc_msgsend(abird, @selector(fly));
无论是 objective c 的消息机制还是 qt 中的 signal/slot 机制,可以说都是在尝试将对象本身(数据)与对对象的操作(消息)解耦,但 go 将这个工作在语言层面做得更加彻底,这样不仅避免多重继承问题,还体现出面向对象设计中最要紧的事情:对象间的消息传递。
实现
interface 实际上就是一个结构体,包含两个成员。其中一个成员是指向具体数据的指针,另一个成员中包含了类型信息。空接口和带方法的接口略有不同,下面分别是空接口和带方法的接口是使用的数据结构:
struct eface { type* type; void* data; }; struct iface { itab* tab; void* data; }; struct itab { interfacetype* inter; type* type; itab* link; int32 bad; int32 unused; void (*fun[])(void); }; struct type { uintptr size; uint32 hash; uint8 _unused; uint8 align; uint8 fieldalign; uint8 kind; alg *alg; void *gc; string *string; uncommontype *x; type *ptrto; };
先看eface,它是interface{}底层使用的数据结构。数据域中包含了一个void*指针,和一个类型结构体的指针。interface{}扮演的角色跟c语言中的void*是差不多的,go中的任何对象都可以表示为interface{}。不同之处在于,interface{}中有类型信息,于是可以实现反射。
不同类型数据的类型信息结构体并不完全一致,type是类型信息结构体中公共的部分,其中size描述类型的大小,uncommontype是指向一个函数指针的数组,收集了这个类型的具体实现的所有方法。
在reflect包中有个kindof函数,返回一个interface{}的type,其实该函数就是简单的取eface中的type域。
iface和eface略有不同,它是带方法的interface底层使用的数据结构。data域同样是指向原始数据的,itab中不仅存储了type信息,而且还多了一个方法表fun[]。一个iface中的具体类型中实现的方法会被拷贝到itab的fun数组中。
type的uncommontype中有一个方法表,某个具体类型实现的所有方法都会被收集到这张表中。reflect包中的method和methodbyname方法都是通过查询这张表实现的。表中的每一项是一个method,其数据结构如下:
struct method { string *name; string *pkgpath; type *mtyp; type *typ; void (*ifn)(void); void (*tfn)(void); };
iface的itab的interfacetype中也有一张方法表,这张方法表中是接口所声明的方法。其中每一项是一个imethod,数据结构如下:
struct imethod { string *name; string *pkgpath; type *type; };
跟上面的method结构体对比可以发现,这里是只有声明没有实现的。
iface中的itab的func域也是一张方法表,这张表中的每一项就是一个函数指针,也就是只有实现没有声明。
类型转换时的检测就是看type中的方法表是否包含了interfacetype的方法表中的所有方法,并把type方法表中的实现部分拷到itab的func那张表中。
注意事项
一个interface在没有进行初始化时,对应的值是nil。也就是说:
var v interface{}
此时v就是一个nil。在底层存储上,它是一个空指针。
与之不同的情况
var obj *t var v interface{} v = obj
此时v是一个interface,它的值是nil,也就是说其data域为空,但它自身不为nil。
下面来看个例子就明白了:
go语言中的error类型实际上是抽象了error()方法的error接口:
type error interface { error() string }
有如下代码:
type error struct { errcode uint8 } func (e *error) error() string { switch e.errcode { default: return "unknown error" } } func test_checkerror() { var e *error if e == nil { fmt.println("e is nil") } else { fmt.println("e is not nil") } var err error err = e if err == nil { fmt.println("err is nil") } else { fmt.println("err is not nil") } }
运行test_checkerror()输出:
e is nil
err is not nil
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 物联网时代,开发者应该怎么做?
推荐阅读
-
Android 自动判断是电话,网址,EMAIL方法之Linkify的使用
-
Linux磁盘管理之df命令详细介绍和使用实例(统计文件或目录的磁盘占用情况)
-
Android开发之OkHttpUtils的具体使用方法
-
Android开发之XML文件解析的使用
-
Android笔记之:App自动化之使用Ant编译项目多渠道打包的使用详解
-
Android控件之ToggleButton的使用方法
-
搞清楚 Python traceback的具体使用方法
-
Python3.5基础之函数的定义与使用实例详解【参数、作用域、递归、重载等】
-
ThinkPHP3.1新特性之G方法的使用
-
ThinkPHP3.1新特性之命名范围的使用