Go之interface的具体使用
浅显地了解了一下Go,发现Go语法的设计非常简洁,易于理解。正应了Go语言之父RobPike说的那句“Lessismore”——大道至简。
下面就具体的语法特性说说我自己的体会。
interface
概览
与通常以类型层次与继承为根基的面向对象设计(OOP)语言(如C++、Java)不同,Go的核心思想就是组合(composition)。Go进一步解耦了对象与操作,实现了真正的鸭子类型(Ducktyping):一个对象如果能嘎嘎叫那就能当做鸭子,而不是像C++或Java那样需要类型系统去保证:一个对象先得是只鸭子,然后才能嘎嘎叫。
typeDuckinterface{
Quack()
}
typeAnimalstruct{
namestring
}
func(animalAnimal)Quack(){
fmt.Println(animal.name,":Quack!Quack!Likeaduck!")
}
funcmain(){
unknownAnimal:=Animal{name:"Unknown"}
varequivalentDuck
equivalent=unknownAnimal
equivalent.Quack()
}
运行上面的代码输出:
Unknown:Quack!Quack!Likeaduck!
下面用Java语言来实现:
interfaceDuck{
voidQuack();
}
classSomeAnimalimplementsDuck{
Stringname;
publicSomeAnimal(Stringname){
this.name=name;
}
publicvoidQuack(){
System.out.println(name+":Quack!Quack!Iamaduck!");
}
}
publicclassTest{
publicstaticvoidmain(String[]args){
SomeAnimalunknownAnimal=newSomeAnimal("Unknown");
Duckequivalent=unknownAnimal;
equivalent.Quack();
}
}
两相比较就能看出:Go将对象与对其的操作(方法或函数)解耦得更彻底。Go并不需要一个对象通过类型系统来保证实现了某个接口(isa),而只需要这个对象实现了某个接口的方法即可(likea),而且类型声明与方法声明或实现也是松耦合的形式。如果稍微转换一下方法的实现方式:
func(animalAnimal)Quack(){
fmt.Println(animal.name,":Quack!Quack!Likeaduck!")
}
为:
funcQuack(animalAnimal){
fmt.Println(animal.name,":Quack!Quack!Likeaduck!")
}
是不是就和普通方法并无二致了?
在深入浅出Cocoa之消息一文中我曾分析过ObjectiveC的消息调用过程:
Bird*aBird=[[Birdalloc]init]; [aBirdfly];
中对fly的调用,编译器通过插入一些代码,将之转换为对方法具体实现IMP的调用,这个IMP是通过在Bird的类结构中的方法链表中查找名称为fly的选择子SEL对应的具体方法实现找到的,编译器会将消息调用转换为对消息函数objc_msgSend的调用:
objc_msgSend(aBird,@selector(fly));
无论是ObjectiveC的消息机制还是Qt中的Signal/Slot机制,可以说都是在尝试将对象本身(数据)与对对象的操作(消息)解耦,但Go将这个工作在语言层面做得更加彻底,这样不仅避免多重继承问题,还体现出面向对象设计中最要紧的事情:对象间的消息传递。
实现
interface实际上就是一个结构体,包含两个成员。其中一个成员是指向具体数据的指针,另一个成员中包含了类型信息。空接口和带方法的接口略有不同,下面分别是空接口和带方法的接口是使用的数据结构:
structEface
{
Type*type;
void*data;
};
structIface
{
Itab*tab;
void*data;
};
structItab
{
InterfaceType*inter;
Type*type;
Itab*link;
int32bad;
int32unused;
void(*fun[])(void);
};
structType
{
uintptrsize;
uint32hash;
uint8_unused;
uint8align;
uint8fieldAlign;
uint8kind;
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,其数据结构如下:
structMethod
{
String*name;
String*pkgPath;
Type*mtyp;
Type*typ;
void(*ifn)(void);
void(*tfn)(void);
};
Iface的Itab的InterfaceType中也有一张方法表,这张方法表中是接口所声明的方法。其中每一项是一个IMethod,数据结构如下:
structIMethod
{
String*name;
String*pkgPath;
Type*type;
};
跟上面的Method结构体对比可以发现,这里是只有声明没有实现的。
Iface中的Itab的func域也是一张方法表,这张表中的每一项就是一个函数指针,也就是只有实现没有声明。
类型转换时的检测就是看Type中的方法表是否包含了InterfaceType的方法表中的所有方法,并把Type方法表中的实现部分拷到Itab的func那张表中。
注意事项
一个interface在没有进行初始化时,对应的值是nil。也就是说:
varvinterface{}
此时v就是一个nil。在底层存储上,它是一个空指针。
与之不同的情况
varobj*T
varvinterface{}
v=obj
此时v是一个interface,它的值是nil,也就是说其data域为空,但它自身不为nil。
下面来看个例子就明白了:
Go语言中的error类型实际上是抽象了Error()方法的error接口:
typeerrorinterface{
Error()string
}
有如下代码:
typeErrorstruct{
errCodeuint8
}
func(e*Error)Error()string{
switche.errCode{
default:
return"unknownerror"
}
}
functest_checkError(){
vare*Error
ife==nil{
fmt.Println("eisnil")
}else{
fmt.Println("eisnotnil")
}
varerrerror
err=e
iferr==nil{
fmt.Println("errisnil")
}else{
fmt.Println("errisnotnil")
}
}
运行test_checkError()输出:
eisnil
errisnotnil
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。