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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Go语言中你不知道的Interface详解
  • golang中interface接口的深度解析
  • Go语言interface 与 nil 的比较
  • 浅谈Go语言中的结构体struct & 接口Interface & 反射
  • go语言中的interface使用实例
  • Go语言interface详解
(0)

相关推荐

  • Go语言中你不知道的Interface详解

    前言 最近在看Go语言的面向对象的知识点时,发现它的面向对象能力全靠 interface 撑着,而且它的 interface 还与我们以前知道的 interface 完全不同.故而整个过程不断的思考为什么要如此设计?这样设计给我们带来了什么影响? interface 我不懂你 Rob Pike 曾说: 如果只能选择一个Go语言的特 性移植到其他语言中,他会选择接口 被Go语言设计者如此看重,想来 interface 一定是资质不凡,颜值爆表.但是说实话,当我第一次读这部分内容的时候,我产生了以下

  • Go语言interface 与 nil 的比较

    interface简介 Go语言以简单易上手而著称,它的语法非常简单,熟悉C++,Java的开发者只需要很短的时间就可以掌握Go语言的基本用法. interface是Go语言里所提供的非常重要的特性.一个interface里可以定义一个或者多个函数,例如系统自带的io.ReadWriter的定义如下所示: type ReadWriter interface { Read(b []byte) (n int, err error) Write(b []byte) (n int, err error)

  • golang中interface接口的深度解析

    一 接口介绍 如果说gorountine和channel是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道亮丽的风景,那么接口是Go语言整个类型系列的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度.Go语言在编程哲学上是变革派,而不是改良派.这不是因为Go语言有gorountine和channel,而更重要的是因为Go语言的类型系统,更是因为Go语言的接口.Go语言的编程哲学因为有接口而趋于完美.C++,Java 使用"侵入式"接口,主要表现在实现

  • Go语言interface详解

    interface Go语言里面设计最精妙的应该算interface,它让面向对象,内容组织实现非常的方便,当你看完这一章,你就会被interface的巧妙设计所折服. 什么是interface 简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为. 我们前面一章最后一个例子中Student和Employee都能SayHi,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能say hi 让我们来继续做更多的扩展,Student和Employe

  • go语言中的interface使用实例

    go语言中的interface是一组未实现的方法的集合,如果某个对象实现了接口中的所有方法,那么此对象就实现了此接口.与其它面向对象语言不同的是,go中无需显示声明调用了哪个接口. 复制代码 代码如下: package main   import (  "fmt" )   type I interface {  Get() int  Put(int) }   type S struct{ i int }   func (p *S) Get() int  { return p.i } f

  • 浅谈Go语言中的结构体struct & 接口Interface & 反射

    结构体struct struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套: go中的struct类型理解为类,可以定义方法,和函数定义有些许区别: struct类型是值类型. struct定义 type User struct { Name string Age int32 mess string } var user User var user1 *User = &User{} var user2 *User = new(User) struct使用 下面示例中user1和

  • java中的interface接口实例详解

     java中的interface接口实例详解 接口:Java接口是一些方法表征的集合,但是却不会在接口里实现具体的方法. java接口的特点如下: 1.java接口不能被实例化 2.java接口中声明的成员自动被设置为public,所以不存在private成员 3.java接口中不能出现方法的具体实现. 4.实现某个接口就必须要实现里面定义的所有方法. 接下来看一个实现接口的案例: package hello;   interface competer{ //定义接口 void set_comp

  • 详解 objective-c中interface与protocol的作用

    详解 objective-c中interface与protocol的作用 以前对Objective-C中的interface,即头文件的作用一直不太清楚.最近看了一些文章,再加上自己的试验,对头文件的作用稍有了解. 在我看来,头文件的作用是,定义对外的接口. 然而,它的作用也只有这个而已.头文件无法保证对外接口一定会被实现. 根据.h文件是否定义方法..m文件是否实现方法,可以分为三类: 第一类是.h文件定义方法,.m文件也实现了方法,这是最common的做法,也是最没有问题的做法. 第二类是.

  • 深入解析Java接口(interface)的使用

    Java接口(interface)的概念及使用 在抽象类中,可以包含一个或多个抽象方法:但在接口(interface)中,所有的方法必须都是抽象的,不能有方法体,它比抽象类更加"抽象". 接口使用 interface 关键字来声明,可以看做是一种特殊的抽象类,可以指定一个类必须做什么,而不是规定它如何去做. 现实中也有很多接口的实例,比如说串口电脑硬盘,Serial ATA委员会指定了Serial ATA 2.0规范,这种规范就是接口.Serial ATA委员会不负责生产硬盘,只是指定

  • Java abstract class 与 interface对比

    Java abstract class 与 interface对比 前言 abstract class和interface都是Java用来描述抽象体的,不知道是否有同学跟我一样对这两者的语法层面的区别以及如何选择这两者还存在着疑惑,反正接下来,让我详细介绍一下abstract class和interface. 理解抽象类 在面向对象的概念中,所有的对象都是通过类来描述的.但是反过来却不是这样,并不是所有的类都是用来描述对象的.因为这个类中可能没有足够的信息来描述一个具体的对象,这样的类就是抽象类

  • 领悟php接口中interface存在的意义

    可能大家都懂这些,作为不懂的我猜测了一下这个interface的意义,他就是为了后面调用的时候再调用的方法中调用实现类中interface中存在的内容,好绕口啊,写个例子留作以后看吧pay.php 复制代码 代码如下: interface Ipay{ function withmoney(); //function withinternet();}class Dmeng implements Ipay{ function withmoney() {  echo "花人民币买东西"; }

  • PHP SPL标准库之接口(Interface)详解

    PHP SPL标准库总共有6个接口,如下: 1.Countable 2.OuterIterator 3.RecursiveIterator 4.SeekableIterator 5.SplObserver 6.SplSubject 其中OuterIterator.RecursiveIterator.SeekableIterator都是继承Iterator类的,下面会对每种接口作用和使用进行详细说明. Coutable接口: 实现Countable接口的对象可用于count()函数计数. 复制代码

  • C#接口(Interface)用法分析

    本文实例分析了C#接口(Interface)用法.分享给大家供大家参考.具体分析如下: 继承"基类"跟继承"接口"都能实现某些相同的功能,但有些接口能够完成的功能是只用基类无法实现的 1.接口用于描述一组类的公共方法/公共属性. 它不实现任何的方法或属性,只是告诉继承它的类至少要实现哪些功能,继承它的类可以增加自己的方法. 2.使用接口可以使继承它的类: 命名统一/规范,易于维护.比如: 两个类 "狗"和"猫",如果它们都继承

随机推荐