深度剖析interface

shanks · · 2048 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

什么是interface

想象你要在路边打一辆出租车从A地到B地,你不需要知道出租车司机的国籍、性别,是人还是机器人,只要他能够把你从A地带到B地就可以。
golang中的interface就是这个意思,你不要一个特定的类型,而是需要它实现你想要的功能。interface可以理解为你想要的功能集合。

  1. //interface 定义你想要的功能
  2. type Driver interface {
  3. //能够把你从A地带到B地
  4. Drive(from Location, to Location)
  5. }
  6. //只要实现了你想要功能(Drive)的类型都满足你的要求
  7. func TakeRide(from Location, to Location, driver Driver){
  8. driver.Drive(from, to)
  9. }
  10. //定义两个能够Drive的类型
  11. type Human struct {
  12. }
  13. func (p *Human) Drive(from Location, to Location){
  14. //implements the drive method
  15. }
  16. type Robot struct {
  17. }
  18. func (p *Robot) Drive(from Location, to Location){
  19. //implements the drive method
  20. }
  21. func main(){
  22. var A Location
  23. var B Location
  24. var random_human_driver *Human = new(Human)
  25. var random_robot_driver *Robot = new(Robot)
  26. //不管是人还是机器人只要能够开车都能满足你的要求
  27. TakeRide(A, B, random_human_driver)
  28. TakeRide(A, B, random_robot_driver)
  29. }

与C++ java等传统面向对象的语言区别在于,一个Human不需要显示声明自己能够Drive(继承自Drive接口),只要它能够Drive(Human类型必须实现Drive函数)。
这种”非侵入式接口“的优点在于接口的定义与类型的定义分离:
1. 定义接口变成一件很容的事,你需要什么功能就定义一个接口,只要实现了这个接口的类型都能满足要求。golang标准类库中的接口很多接口只有一个函数。
2. 定义类型的时候是要关注这个类型应该提供什么方法,不需要考虑它会用在什么地方。

类型断言

  1. func TakeRide(from Location, to Location, driver Driver){
  2. driver.Drive(from, to)
  3. }

在这个函数中driver具体是个Human还是个Robot是看不出来的,你希望司机如果是个人的话能够陪你聊聊天,怎么知道它是不是个人呢?
使用Type Assert可以判断一个interface是不是具体的类型

v, ok := driver.(*Human)
如果driver实际上确实是个Human,那么v就会指向具体的Human类型,ok也会设置为true
如果driver实际上是个Robot,那么human就会为nil,ok也会设置为false

这时候你又有新的要求了,希望司机如果是个人的话就陪你聊天,如果是个机器人的话就给你唱歌。
当然可以使用上面的 Type Assert 实现,但是要写if else 代码很丑,golang中有更优雅的实现方式 Type Switches

  1. switch v:= driver.(type) {
  2. case *Human:
  3. v.Talk()
  4. case *Robot:
  5. v.Sing()
  6. default:
  7. fmt.Println("don't know the type but it can drive")
  8. }
  9. //当然这需要Human需要能够Talk,Robot能够Sing才可以
  10. func (p *Human) Talk(){
  11. fmt.Println("talk something with you")
  12. }
  13. func (p *Robot) Sing(){
  14. fmt.Println("sing a song for you")
  15. }

interface 具体实现

在go语言中会为每一种类型(Human Robot int…..)定义一个描述该类型信息的结构体,该结构体中包含这个类型所实现的所有方法。

  1. //MemberInfo 对应一个具体的方法,将一个方法名与方法的具体地址对应起来
  2. typedef struct _MemberInfo {
  3. const char * tag;
  4. void * addr;
  5. } MemberInfo;
  6. //TypeInfo 对应一个具体的类型,一个具体的类型包含一个MemberInfo数组
  7. typedef struct _TypeInfo {
  8. // 用于运行时取得类型信息, 比如反射机制
  9. MemberInfo* members;
  10. } TypeInfo;
  11. //那么Human这个类型的描述i信息就可以定义为这样了
  12. TypeInfo HumanTypeInfo ;
  13. HumanTypeInfo.members = { {"Drive", "Human Driver func address "}, {"Talk","Human Talk func address"}}

同时编译器也会为每一种interface类型定义一个结构体描述该interface所包括的函数集合

  1. typedef struct _InterfaceInfo {
  2. // 用于运行时取得interface信息
  3. const char ** tags;
  4. } InterfaceInfo;
  5. InterfaceInfo DriveInterfaceInfo;
  6. DriveInterfaceInfo.tags ={"Drive"}

定义好上诉两种meta info 以后,我们就发现很容易判断一个具体的类型是否满足一个接口了,只要找到这个类型的类型信息中所实现的方法集是否完全包含这个接口信息所定义的方法集。假如某类型有m个方法,某接口有n个方法,则很容易知道这种判定的时间复杂度为O(mXn),不过可以使用预先排序的方式进行优化,实际的时间复杂度为O(m+n)。

  1. var driver Driver
  2. driver = &Human{}
  3. //判断下Human所实现的方法集{Drive Talk} 是否满足Driver这个Interface所定义的功能{Drive}

var driver Driver
这个driver interface变量的具体内存结构又是怎样的呢
先来看下这个结构体的定义

  1. typedef struct _IDriver {
  2. IDriverTbl* tab;
  3. void* data;
  4. } IDriver;
  5. typedef struct _IDriverTbl {
  6. InterfaceInfo* inter;
  7. TypeInfo* type;
  8. void (*Drive)();
  9. } IDriverTbl;

一个interface变量由两部分组成,一个是data,是该interface实际指向的具体类型的内存地址(*Human)
一个是tab,tab中包含interface元信息(DriveInterfaceInfo)
具体类型的元信息(HumanTypeInfo)
以及该接口所包含的具体函数的实际地址(在这个例子中是Human的Drive函数的地址)
因为含有具体类型的元信息所以类型断言也就能够实现了,driver.(*Human)只要取出driver.tab->type判断是否与*Human一致就可以了

  1. IDriver IHumanDriver
  2. IHumanDriver.data = &Human{}
  3. IHumanDriver.tab={&DriveInterfaceInfo,&HumanTypeInfo,“HumanTypeInfo中保存的Drive函数的具体地址”}

当调用driver.Drive()这个函数时实际上内部的代码就会变为
IHumanDriver.tab->Drive(IHumanDriver.data)

那么这个IHumanDriver结构体是在什么时候生成的呢?golang选择在运行时生成它,当首次遇见 driver := &Human{} 这样的语句时golang会生成Driver接口对应于Human类型的IHumanDriver结构体,并将齐缓存起来。等到下在遇到driver := &Human{}这样的语句是就可以从内存把IHumanDriver这个结构取出就可以用了。

参考文献

深入理解interface
Learning Go - Interfaces & Reflections
为什么我不喜欢Go语言式的接口

Go Data Structures: Interfaces

《Go语言编程》—许式伟


有疑问加站长微信联系(非本文作者)

本文来自:shanks's blog

感谢作者:shanks

查看原文:深度剖析interface

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2048 次点击  
加入收藏 微博
1 回复  |  直到 2000-01-01 00:00:00
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传