Go之interface的具体使用
作者:飘飘白云 发布时间:2024-02-08 18:58:41
浅显地了解了一下 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
来源:http://blog.csdn.net/kesalin/article/details/79500545


猜你喜欢
- 一般情况下,运行tensorflow时,默认会占用可以看见的所有GPU,那么就会导致其它用户或程序无GPU可用,那么就需要限制程序对GPU的
- 上一次很多朋友写文字屏蔽说到要用正则表达,其实不是我不想用(我正则用得不是很多,看过我之前爬虫的都知道,我直接用BeautifulSoup的
- python的版本及依赖的库的安装#版本python 3.7.1pip install pywin32==224pip install nu
- 在用Python做开发的时候经常会用到数据库或者其他需要动态配置的东西,硬编码在里面每次去改会很麻烦。Python自带有读取配置文件的模块C
- 这里为大家推荐一些不错的学习python基础的网站Python是一种计算机程序设计语言。你可能已经听说过很多种流行的编程语言,比如非常难学的
- console.log('----1----') console.log('12' + '34
- 前言本篇给大家分享一下《通过Python的pdfplumber库将pdf转为图片》。一、pdfplumber库是什么?pdfplumber是
- 控制的前提是已经运行Microsip.exe 首先选择文件,选择txt格式文件,一行一个手机号格式;如下点击拨打下一个
- 用re或者string.find.以下是re代码import re#文本所在TXT文件file = '123.txt'#关键
- 前言:jieba是优秀的中文分词第三方库,由于中文文本之间每个汉字都是连续书写的,我们需要通过特定的手段来获得其中的每个词组,这种手段叫做分
- 上一篇实战爬取知乎热门话题的实战,并且保存为本地的txt文本先上代码,有很多细节和坑需要规避,弄了两个半小时import requestsi
- 大家都知道,在通过Pyqt4的designer工具创建界面.ui文件后需要手动cmd命令将.ui文件转换为.py之后才能进行事件的编写,如果
- 一、集合在 python 中用 {} 扩起一堆数字,但是这堆数字没有体现映射关系,那么这堆数字就是一个集合。集合的特色:集合在 python
- 描述:下午快下班的时候公司供应链部门的同事跑过来问我能不能以程序的方法帮他解决一些excel表格每周都需要手工重复做的事情,Excel 是数
- 本文介绍了Sublime开发python程序的示例代码,分享给大家,具体如下:下载、安装Python程序https://www.python
- PromisePromise能够处理异步程序。回调地狱JS中或node中,都大量的使用了回调函数进行异步操作,而异步操作什么时候返回结果是不
- 导语"? 花草树木 皆有呈名热爱自然,从认识自然开始 "现在的植物爱好者,遇到不认得的植物。怎么办呢?前几天去逛商场,一
- 背景我们先来看看MySQL 8.0的事务提交的大致流程以上流程,是MySQL8.0对WAL原则的一种实现,这个流程意味着,任何一个事务的提交
- 本文实例讲述了Joomla开启SEF的方法。分享给大家供大家参考,具体如下:使用SEF(search engine friendly)网址的
- 大家在写按钮(input、button)的时候会发现在 IE 下:随着字数的增多,两边的间距也会越来越大。在 WIN 的XP 风格下,当字数