接口为Go语言提供了多态特性,多态是指代码可以根据类型的具体实现采用不同行为的能力。如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值。

鸭子类型 Duck Typing

f it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

也就是说,如果一个类型实现了某个接口所定义的所有方法时,就说这个类型实现了该接口,而无需显式地声明某个类型实现了某个接口。Golang将类型检查的工作交给了编译器,在调用某个方法时,编译器会将对应的类型隐式地转换为对应接口的类型。

实现

接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。

  1. //interface definition
  2. type VowelsFinder interface {
  3. FindVowels() []rune
  4. }
  5. type MyString string
  6. //MyString implements VowelsFinder
  7. func (ms MyString) FindVowels() []rune {
  8. var vowels []rune
  9. for _, rune := range ms {
  10. if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
  11. vowels = append(vowels, rune)
  12. }
  13. }
  14. return vowels
  15. }

调用FindVowels函数

  1. func main() {
  2. name := MyString("Sam Anderson")
  3. var v VowelsFinder
  4. v = name // possible since MyString implements VowelsFinder
  5. fmt.Printf("Vowels are %c", v.FindVowels())
  6. }

值接收者和指针接收者

各类源码分析写的太复杂了,这里先记录一个方便记忆的思路:
在Golang中,指针类型的值可以解引用为其指向的目标值,
而方法的调用,实际是将接收者进行值拷贝后执行对应的函数,因此,如果一个方法的接收者是值类型的,那么使用对应类型值的指针去调用该方法是可行的,编译器会先将该指针拷贝过来,然后将指针解引用为对应的值,然后执行函数,反之则无法根据拷贝的值找到对应的指针,那么也就无法执行该方法了。
举个例子:
定义一个接口coder包含两个方法codedebug :

  1. type coder interface {
  2. code()
  3. debug()
  4. }

定义一个结构体Gopher去实现这个接口:

  1. type Gopher struct {
  2. language string
  3. }

使用值接收者来实现接口coder

  1. func (g Gopher) code(){
  2. ...
  3. }
  4. func (g Gopher) debug() {
  5. ...
  6. }

那么,下面两种方式都能正常调用

  1. func main() {
  2. var gp = &Gopher{}
  3. var g = Gopher{}
  4. // g和gp均能正常调用code()和debug()方法
  5. }

反之,如果使用Goper类型的指针作为接收者,那么就不能使用Goper的值类型调用code()和debug()

  1. func (g *Gopher) code(){
  2. ...
  3. }
  4. func (g *Gopher) debug() {
  5. ...
  6. }
  7. func main() {
  8. var gp = &Gopher{}
  9. var g = Gopher{}
  10. // 执行g.code()会报错,提示g并未实现coder接口
  11. // 当gp调用对应的方法时,会同时修改原始gp的值
  12. }

空接口

空接口没有定义任何函数,那么Golang中的任何类型都实现了一个空接口
对空接口的类型转换被称为断言。
看一个例子:

  1. type Person struct {
  2. name string
  3. age int
  4. }
  5. var i = interface{}
  6. s = i.(Person)

断言的语法为: <目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言 <目标类型的值> := <表达式>.( 目标类型 )   //非安全类型断言

eface和iface (暂时未整理)

方法能给用户自定义的类型添加一些行为,给一个函数添加一个接收者,那么这个函数就变成了这个类型的方法,
interface分为空接口和包含方法的接口两大类型,在go语言底层,这两大类型分别用eface和iface指定:

  1. struct Eface
  2. {
  3. Type* type;
  4. void* data;
  5. };
  6. struct Iface
  7. {
  8. Itab* tab;
  9. void* data;
  10. };

因此,go语言中的interface组成结构可以抽象为两类:

  • 包含已存储值的类型信息及方法集的内部表iTable
  • 所存储的值数据

综上,go语言的interface类型可以抽象如下图
屏幕快照 2019-06-26 下午2.20.56.png
eface和iface的区别仅仅在方法集的有无

eface

举例说明无方法集的接口赋值过程

  1. import (
  2. "fmt"
  3. "strconv"
  4. )
  5. type Binary uint64
  6. func main() {
  7. b := Binary(200)
  8. any := (interface{})(b)
  9. fmt.Println(any)
  10. }

输出结果为200,赋值后的结构如下图
屏幕快照 2019-06-26 下午2.23.51.png

iface

如果一个类型实现了interface中所有的方法,可以认为该类型实现了该interface

  1. // 定义一个通知类行为的接口notifier
  2. type notifier interface {
  3. notify()
  4. }
  5. // 定义一个用户类结构体user
  6. type user struct {
  7. name string
  8. email string
  9. }
  10. // 使用user指针接受者实现notify方法,从而实现notifier接口
  11. // 注意,这里实现notifier接口的是user指针而非user本身
  12. func (u *user) notify() {
  13. fmt.Printf("Sending user email to %s<%s>\n,
  14. u.name,
  15. u.email)
  16. }
  17. // 接受一个实现了notifier接口的值并发送通知
  18. func sendNotification(n notifier) {
  19. n.notify()
  20. }
  21. u := user{"Bill", "bill@email.com"}
  22. // 错误的调用方法
  23. sendNotification(u)
  24. // 正确的调用方法
  25. sendNotification(&u)

通过一个例子来展示iface结构的赋值过程

  1. package main
  2. import (
  3. "fmt"
  4. "strconv"
  5. )
  6. type Binary uint64
  7. func (i Binary) String() string {
  8. return strconv.FormatUint(i.Get(), 10)
  9. }
  10. func (i Binary) Get() uint64 {
  11. return uint64(i)
  12. }
  13. func main() {
  14. b := Binary(200)
  15. any := fmt.Stringer(b)
  16. fmt.Println(any)
  17. }
  18. /*
  19. // fmt.Stringer是一个包含String方法的接口
  20. type Stringer interface {
  21. String() string
  22. }
  23. */

屏幕快照 2019-06-26 下午3.20.02.png

方法集

方法集定义了接口接受的规则,参考上一节代码,由于实现notify方法的是user的指针类型而非user类型,因此不能将u(user类型)作为参数传递至sendNotification,参考方法集的描述

Values Methods Receivers
T (t T)
*T (t T) and (t *T)

由此可见,指向T类型的指针方法集可接收的类型包含T和*T两种类型,这里提现了go语言底层对指针类型数据的处理方式,而非方法集本身特有属性。

参考

go in action
https://i6448038.github.io/2018/10/01/Golang-interface/