[Go]接口的运用

Posted yuxiaoba

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Go]接口的运用相关的知识,希望对你有一定的参考价值。

  在Go语言中,不能通过调用new函数或make函数创建初一个接口类型的值,也无法用字面量来表示一个接口类型的值。可以通过关键字type和interface声明接口类型,接口类型的类型字面量与结构体类型有些相似,不过结构体类型包裹的是它的字段声明,而接口类型包裹的是它的方法定义。

  接口类型声明中的这些方法所代表的就是该接口的方法集合,一个接口的方法集合就是它的全部特征。对任何数据类型,只要它的方法集合中完全包含了一个接口的全部方法,那么它就一定是这个接口的实现类型。

type Pet interface {
    SetName(name string)
    Name() string
    Category() string
}

  这里声明了一个接口类型Pet,它包含了3个方法定义,这3个方法共同组成了接口类型Pet的方法集合,只要一个数据类型的方法集合中有这3个方法,那么它就一定是Pet接口的实现类型。这是一种无侵入式的接口实现方式。 

  那怎样判定一个数据类型的某个方法实现的就是某个接口类型中的某个方法呢?有两个充分必要条件:

      1)两个方法的签名需要完全一致

      2)两个方法的名称要一模一样

type Dog struct {
    name string // 名字。
}

func (dog *Dog) SetName(name string) {
    dog.name = name
}

func (dog Dog) Name() string {
    return dog.name
}

func (dog Dog) Category() string {
    return "dog"
}

  Dog类型本身的方法集合中只包含了2个值方法,而它的指针类型*Dog方法集合却包含了3个方法(两个值方法加一个指针方法),又由于这3个方法恰恰分别是Pet接口中某个方法的实现,所以*Dog类型就成为了Pet接口的实现类型。

dog := Dog{"little pig"}
var pet Pet = &dog

  因此可以声明并初始化一个Dog类型的变量dog,然后把它的指针值赋给类型为Pet的变量pet。

  对于一个接口类型的变量,赋给它的值可以被叫做它的实际值,而该值的类型可以被叫做这个变量的实际类型(动态类型)

  如把&dog的结果值赋给了变量pet,这个结果值就是变量pet的动态值,*Dog就是该变量的动态类型。对于变量pet来说,它的静态类型就是Pet,并且永远是Pet,但是它的动态类型会随着我们赋给它的动态值而变化,并且在给一个接口类型的变量赋予实际的值之前,它的动态类型是不存在的。

 

1、当为一个接口变量赋值时会发生什么?

  假设把Pet接口声明简化一下

type Pet interface {
    Name() string
    Category() string
      //去掉了SetName方法
}        

  这时

    dog := Dog{"little pig"}var pet Pet = dog
    dog.SetName("monster")

  这时pet变量的字段name的值依然时”little pig“

  由于dog的SetName方法时指针方法,所以该方法的持有的接收者就是指向dog的指针值的副本,因而其中对接收者的name字段的设置就是对变量dog的改动。那么当dog.SetName执行之后,dog的name字段的值就一定是”monster“。

  那为什么dog的name变量,而pet却没有呢?

  如果使用一个变量给另外一个变量赋值,那么真正赋给后者的,并不是前者持有的那个值,而是该值的一个副本。 例如

dog1 := Dog{"litte dog"}
dog2  := dog1
dog1.name = "monster"

  此时dog2的值仍然回收”litter dog“

  接口类型本身时无法被值化的,在赋予它实际的值之前,它的值一定会是nil,这是它的零值。

  当给接口变量赋值时,该变量的动态类型会与它的动态值一起被存储在一个专用的数据结构中。这样的一个变量的值其实时这个专用数据结构的一个实例,而不是我们赋给该变量的那个实际的值。所以pet的值与dog值肯定是不同的,无论从它们存储的内容还是存储的结构。

  Go语言把这个专用的数据结构叫做iface,iface会包含两个指针,一个时指向类型信息的指针,另一个时指向动态值的指针。这里的类型信息由另一个专用数据结构的实例承载,其中包含了动态值的类型,以及使它实现了接口的方法和调用它们的途径。

  总之,接口变量被赋予动态值的时候,存储的是包含这个动态值的副本的一个结构更加复杂的值。

 

2、接口变量的值在什么情况下才真正为nil?

    var dog1 *Dog
    fmt.Println("The first dog is nil.")
    dog2 := dog1
    fmt.Println("The second dog is nil.")
    var pet Pet = dog2
    if pet == nil {
        fmt.Println("The pet is nil.")
    } else {
        fmt.Println("The pet is not nil.")
    }

  dog1和dog2是nil很好理解,那pet会是nil吗?

  pet的值不会是nil,因为这个动态值只是pet值的一部分。此时pet动态类型是*Dog

  把nil赋值给了pet,但pet的值却不是nil,这不是很奇怪吗?

  在Go语言中,把字面量nil表示的值叫做无类型的nil,这是真正的nil,因为它的类型也是nil。虽然dog2的值是真正的nil,但当我们把这个变量赋给pet的时候,Go语言会把它的类型和值放在一起考虑。即此时Go语言会识别初赋予pet的值是一个*Dog类型的nil,然后Go语言会用一个iface实例包装它,包装后的产物肯定就不是nil了。

  因而只要把一个有类型的nil赋给接口变量,那这个变量的值就一定不会是真正的nil。

  那要怎样才能让一个接口变量的值真正为nil呢?要么只声明它当不做初始化,要么之间把字面量nil赋给它。

 

3、怎样实现接口之间的组合?

  接口类型间的嵌入被称为接口的组合。接口的组合不会出现屏蔽现象

type Animal interface {
    // ScientificName 用于获取动物的学名。
    ScientificName() string
    // Category 用于获取动物的基本分类。
    Category() string
}

type Named interface {
    // Name 用于获取名字。
    Name() string
}

type Pet interface {
    Animal
    Named
}

 

  

 

以上是关于[Go]接口的运用的主要内容,如果未能解决你的问题,请参考以下文章

解决go: go.mod file not found in current directory or any parent directory; see ‘go help modules‘(代码片段

你知道的Go切片扩容机制可能是错的

如何运用领域驱动设计 - 值对象

从父片段到选项卡片段的接口侦听器不起作用

大量实例详解Go反射机制原理与应用

与另一个片段通信的片段接口