用以定义一组方法集合,或者作为一个任意类型用以接受任何类型变量,实现"面向对象"的一些特性。

面向对象

Go语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。但是Go语言里有非常灵活的接口概念,通过它可以实现很多面向对象的特性。很多面向对象的语言都有相似的接口概念,但Go语言中接口类型的独特之处在于它是满足隐式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型;简单地拥有一些必需的方法就足够了。

再者,go语言摒弃了java、c++之类的继承特性,而是倾向于组合来满足面向对象。这个到没有什么特殊性,只能说各有千秋、各有场景。

接口的特性

如何理解go 语言中的interface ? 只需记住以下四点即可:

  1. interface 是方法声明的集合,是一个指针类型
  2. 任何类型的对象实现了在interface 接口中声明的全部方法,则表明该类型实现了该接口,无论是自己实现还是通过"组合"。
  3. interface 可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。
  4. *interface 和 interface不一样,*interface是一个固定类型,只能接受*interface

注意:

a. interface 可以被任意对象实现,一个类型/对象也可以实现多个 interface

b. 方法不能重载,如 eat(), eat(s string) 不能同时存在

实现条件

  1. 接口的方法与实现接口的类型方法格式一致
  2. 接口中所有方法均被实现

struct和interface的关系

  1. 一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
  2. 多个类型可以实现相同的接口。
package main

// Service 一个服务需要满足能够开启和写日志的功能
type Service interface {
   Start()     // 开启服务
   Log(string) // 日志输出
}

// Logger 日志器
type Logger struct {
}

// Log 实现Service的Log()方法
func (g *Logger) Log(l string) {
}

// GameService 游戏服务
type GameService struct {
   Logger // 嵌入日志器
}

// Start 实现Service的Start()方法
func (g *GameService) Start() {
}

// 可以自己实现,但是实现的时候就是"覆盖"
//func (g *GameService) Log(l string) {
//	g.Logger.Log(l)
//}

func main() {
   game := GameService{
      Logger: Logger{},
   }
   game.Log("hello")
   game.Start()
}

开闭原则

开闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。 简单的说就是在修改需求的时候,应该尽量通过扩展来实现变化,而不是通过修改已有代码来实现变化。

调用未来

接口的最大的意义就是实现多态的思想,就是我们可以根据interface类型来设计API接口,那么这种API接口的适应能力不仅能适应当下所实现的全部模块,也适应未来实现的模块来进行调用。 也就是调用未来。

依赖倒置

程序要依赖于抽象接口,不要依赖于具体实现。

空接口与非空接口

interface在使用的过程中,共有两种表现形式:

一种为空接口(empty interface),定义如下:

var MyInterface interface{}

另一种为非空接口(non-empty interface), 定义如下:

type MyInterface interface {
        function()
}

这两种interface类型分别用两种struct表示,空接口为eface, 非空接口为iface.

空接口eface

空接口eface结构,由两个属性构成,一个是类型信息_type,一个是数据信息。其数据结构声明如下:

type eface struct {      //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}

_type属性:是GO语言中所有类型的公共描述,Go语言几乎所有的数据结构都可以抽象成 _type,是所有类型的公共描述,type负责决定data应该如何解释和操作,type的结构代码如下:

type _type struct {
    size       uintptr  //类型大小
    ptrdata    uintptr  //前缀持有所有指针的内存大小
    hash       uint32   //数据hash值
    tflag      tflag
    align      uint8    //对齐
    fieldalign uint8    //嵌入结构体时的对齐
    kind       uint8    //kind 有些枚举值kind等于0是无效的
    alg        *typeAlg //函数指针数组,类型实现的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

data属性: 表示指向具体的实例数据的指针,他是一个unsafe.Pointer类型,相当于一个C的万能指针void*。

非空接口iface

iface 表示 non-empty interface 的数据结构,非空接口初始化的过程就是初始化一个iface类型的结构,其中data的作用同eface的相同,这里不再多加描述。

type iface struct {
  tab  *itab
  data unsafe.Pointer
}

iface结构中最重要的是itab结构(结构如下),每一个 itab 都占 32 字节的空间。itab可以理解为pair<interface type, concrete type> 。itab里面包含了interface的一些关键信息,比如method的具体实现。

type itab struct {
  inter  *interfacetype   // 接口自身的元信息
  _type  *_type           // 具体类型的元信息
  link   *itab
  bad    int32
  hash   int32            // _type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
  fun    [1]uintptr       // 函数指针,指向具体类型所实现的方法
}

其中值得注意的字段,个人理解如下:

  1. interface type包含了一些关于interface本身的信息,比如package path,包含的method。这里的interfacetype是定义interface的一种抽象表示。
  2. type表示具体化的类型,与eface的 type类型相同。
  3. hash字段其实是对_type.hash的拷贝,它会在interface的实例化时,用于快速判断目标类型和接口中的类型是否一致。另,Go的interface的Duck-typing机制也是依赖这个字段来实现。
  4. fun字段其实是一个动态大小的数组,虽然声明时是固定大小为1,但在使用时会直接通过fun指针获取其中的数据,并且不会检查数组的边界,所以该数组中保存的元素数量是不确定的。

sizeof接口,返回值是什么?

package main

import (
	"fmt"
	"unsafe"
)

type PeopleInterface interface {
}

type PeopleInterfaceFunc interface {
	PeopleName() string
}

type Student struct {
}

type Teacher struct {
	PeopleInterface
}

type Member struct {
	Student
}

func main() {
	var p PeopleInterface
	fmt.Println(unsafe.Sizeof(p))

	var pp *PeopleInterface
	fmt.Println(unsafe.Sizeof(pp))

	var s Student
	fmt.Println(unsafe.Sizeof(s))

	var sp *Student
	fmt.Println(unsafe.Sizeof(sp))

	var t Teacher
	fmt.Println(unsafe.Sizeof(t))

	var m Member
	fmt.Println(unsafe.Sizeof(m))

	var pf PeopleInterfaceFunc
	fmt.Println(unsafe.Sizeof(pf))
}

➜  spec git:(master) ✗ ./main 
16
8
0
8
16
0
16

这里是对 unsafe.Sizeof 的一个理解,从文档可以看到sizeof是只求这个入参的数据结构大小,那么针对interface来讲,其实求的是eface或者iface的大小,可以看到都是两个指针,因此输出如下。再者,对于struct来讲,虽然底层肯定也有对应的struct来做支撑,但是这里是一个"显示的"东西,是具有内存占用属性的,所以对struct求sizeof,就是根据成员变量和对齐方式来计算。详细的内存对齐方式,在其他文章专门描述,此处暂且不表。

接口与nil的判定

非常有迷惑性的一个问题,片段一输出p not null,因为返回的是一个空接口,而非一个空指针,接口自身就带有指针特性。要判定这种,需要在为空的时候,就返回 nil,这个才是空指针。函数返回的时候,是一个接口,所以先alloc一个接口,之后,因为要返回的是一个空指针,所以做了类型转换,就成了空接口。改成片段二,则输出 p is null 。这里与这个接口是eface还是iface没有关系,本质都是重新构建一个新的临时对象,赋值为空值,而非赋值为空指针。

下面代码能编译通过么?

不能。因为interface是一个指针,不能接受对象,需要取地址,即:var peo People = &Student{} ,既可以编译通过。

package main

import (
    "fmt"
)

type People interface {
    Speak(string) string
}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {
    if think == "love" {
        talk = "You are a good boy"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var peo People = Stduent{}
    think := "love"
    fmt.Println(peo.Speak(think))
}

interface{}与*interface{}的区别

下面的代码,能否编译通过?

type S struct {
}

func f(x interface{}) {
}

func g(x *interface{}) {
}

func main() {
    s := S{}
    p := &s
    f(s) //A
    g(s) //B
    f(p) //C
    g(p) //D
}
B、D两行错误
B错误为: cannot use s (type S) as type *interface {} in argument to g:
    *interface {} is pointer to interface, not interface

D错误为:cannot use p (type *S) as type *interface {} in argument to g:
    *interface {} is pointer to interface, not interface
看到这道题需要第一时间想到的是Golang是强类型语言,interface是所有golang类型的父类
函数中func f(x interface{})的interface{}可以支持传入golang的任何类型,包括指针,
但是函数func g(x *interface{})只能接受*interface{}

#golang工程师#