乐趣区

关于go:Go语言中的接口类型interface

接口

接口是用来定义行为的类型,定义的行为不禁接口间接实现,而由通过办法由定义的类型实现

Golang 中,接口是一组办法的签名,是语言中一个重要的组成部分,其目标是通过引入一个中间层与具体的实现进行拆散,达到解耦合的作用,同时暗藏底层实现,缩小关注点

Golang 不同于 Java,通过隐式实现申明的接口,即只有实现了接口申明中的办法,就是实现了接口,
接口的定义须要应用 interface 关键字,且在接口中只能定义方法签名,不能蕴含成员变量

基于官网的 io 包进行剖析:


type Reader interface {Read(p []byte) (n int, err error)
}

下面是 io 包中申明的 Reader 接口,如果一个类型须要实现 Reader 接口,那么就仅须要实现 Read(p []byte) (n int, err error) 办法,如 LimitedReader 就实现了 Reader 接口:


type LimitedReader struct {
    R Reader
    N int64
}

func(l *LimitedReader) Read(p []byte) (n int, err error) {
    if ;.N <= 0 {return 0, EOF}
    if int64(len(p)) > l.N {p = p[o:l.N]
    }
    n, err := l.R.Read(p)
    l.N -= int64(n)
    return
}

Golang 只会在参数传递、返回参数和变量赋值时对类型是否实现了某个接口进行查看,接口在定义方法时对实现的接受者做限度,所以会有两种形式实现接口:构造体实现和指针实现。
但这两种实现形式不能够同时存在,Go 语言的编译器会在构造体类型和指针类型都实现同一个办法时报错“method redeclared”


type Cat struct {}
type Duck interface {}

func (c Cat) Quack {} // 构造体实现
func (c *Cat) Quack {} // 指针实现

var d Duck = Cat{} // 构造体初始化
var d Duck = &Cat{} // 指针初始化

留神:指针实现接口,构造体初始化变量是无奈通过编译的;而构造体实现接口,指针初始化变量能够(Golang 在传递参数是值传递的,指针初始化变量时,指针能够隐式地获取到指向的构造体:c.i 能够了解成(*c).i)具体了解就是在 Golang 中,初始化变量后进行办法调用时会产生 ` 值拷贝 `:1. 对于初始化的指针来说,意味着拷贝的新指针依然与原指针一样,指向一个雷同且惟一的构造体,所以编译器能够隐式通过对变量的解援用(dereference)获取到指针的构造体
2. 而对于构造体而言,这是拷贝生成了新的构造体,但办法的参数是指针,编译器既不可能创立一个新的指针,即便创立也无奈指向最后调用该办法的构造体
具体的例子如 Goinaction 的代码示例:
listing36.go
package main

import ("fmt")

type notifier interface {notify()
}

type user struct {
    name string
    email string
}

func (u *user) notify() {fmt.Printf("Sending user email to %s<%s>)\n",
    u.name,
    u.email)
}

func main() {u := user{"Bill", "bill@email.com"}
    
    sendNotification(u)
}

func sendNotification(n notifier) {n.notify()
}
认真查看代码就会发现 u 是一个构造体类型,而 notify 办法是应用指针接受者实现的,上述代码天然就无奈编译通过


数据结构

Golang 依据接口类型是否蕴含一组办法将接口类型分成两类:

  • 应用 runtime.iface 构造体示意蕴含办法的接口

      
      type iface struct {
          tab *itab // runtime.itab 类型构造体,接口类型的外围组成部分
          data unsafe.Pointer // 指向原始数据的指针
      }
  • 应用 runtime.eface 构造体示意不蕴含任何办法的 interface{}类型

      
      type eface struct {
          _type *_type // 指向类型的指针
          data unsafe.Pointer // 指向底层数据的指针
      }

    接口类型不是任意类型

    留神:interface{}类型不是任意类型,如果将类型转换成 interface{}类型,变量在运行期间的类型也会发生变化,获取变量类型时会失去 interface{}

    package main

    type Test struct {}

    func NilOrNot(v interface{}) bool {

      return v == nil

    }

    func main() {

      var s *Test
      fmt.Println(s == nil)
      fmt.Println(NilorNot(s))

    }
    运行上述代码时,第一行打印 true,第二行会打印 false。
    呈现上述景象的起因就是在调用 NilOrNot 函数时产生了隐式的类型转换:*Test 类型转换成 interface{}, 除了这种传参的状况,在变量赋值也是如此

动静派发(Dynamic dispatch)是在运行期间抉择具体多态操作(办法或函数)执行的过程,接口的引入使得 Golang 具备了动静派发的个性,即在调用接口类型的办法时,如果编译期间不能确认接口的类型,则会在运行期间决定具体调用该办法的具体实现

动静派发在构造体上的体现十分差,该当尽量避免应用构造体类型实现接口

退出移动版