Go之interface
Posted 飘飘白云
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go之interface相关的知识,希望对你有一定的参考价值。
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 := Animalname: "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
以上是关于Go之interface的主要内容,如果未能解决你的问题,请参考以下文章