Go与接口:实现接口的条件

779 阅读3分钟

接口类型变量

Go是强类型语言,你不能将整数值赋值给浮点型变量。同样,也不能将没有实现接口的类型值赋值给接口类型变量。

// 1.定义变量是接口类型
var w io.Writer
// 2.将具体类型的值 赋值给这个变量, 要求这个具体类型实现了接口的所有方法
w = os.Stdout
w = new(bytes.Buffer)
w = time.Second         // compile error: time.Duration 没有

var rwc io.ReadWriteCloser
rwc = os.Stdout
rwc = new(bytes.Buffer)  // compile error: *bytes.Buffer lacks Close method


// 等式右边是接口类型
w = rwc
rwc = w     // compile error: io.Writer lacks Close method

关于一个类型持有一个方法的规则

  • T类型的值不持有*T指针的方法
  • *T指针的值持有T类型的方法

所以可能会有*T指针值可以赋值为某接口类型变量而T类型的值不可以的情况

type myWriter struct {}

func (m *myWriter) Write(p []byte) (n int, err error) {
    return
}

func main() {
    var w io.Writer
    w = myWriter{}      // compile error: Cannot use 'myWriter{}' (type myWriter) as the type io.Writer Type does not implement 'io.Writer' as the 'Write' method has a pointer receiver
    w = &myWriter{}     // OK
}

接口类型封装和隐藏具体类型和它的值

接口变量会限制自己只能调用接口类型声明的方法,即便该变量被赋予的值原本有其他方法和成员变量,也无法调用

type myWriter struct {
    scope string
}

func (m *myWriter) Write(p []byte) (n int, err error) {
    return
}

func (m *myWriter) Func() {
	return
}

func main() {
    my := &myWriter{"cn"}
    fmt.Println(my.scope)   // OK
    my.Func()               // Ok
    var w io.Writer
    w = my
    fmt.Println(w.scope)    // compile error: w.scope undefined
    w.Func()                // compile error: w.Func undefined
}

这样的限定就像是对实参进行了一层安全包装,函数内部只知道实参是接口类型,并不知道其他的信息,也无法访问。

从之前的例子可以看到,大接口变量可以被赋值给小接口,被赋值后小接口也只能调用小接口的方法。

空接口存在的意义

空接口(interface{})即没有声明任何方法的接口。因为它什么都没有,所以它可以成为任何。

package main

import (
	"bytes"
	"fmt"
)

func emptyInterfaceIsAny() {
    var any interface{}
    any = true
    fmt.Println(any)
    any = 12.34
    fmt.Println(any)
    any = "hello"
    fmt.Println(any)
    any = map[string]int{"one": 1}
    fmt.Println(any)
    any = new(bytes.Buffer)
    fmt.Println(any)
    }

这很有用,fmt.Println方法就是通过定义参数是空接口类型,才能接收任务类型的实参传递

package main

import "fmt"

func printlnArgsIsEmptyInterface() {
	fmt.Println(1, "hello", []string{"world"})  // 1 hello [world]
}

合理定义接口

接口是对一些有着相同属性实体抽象。那对一些相同属性定义是仁者见仁智者见智,评判抽象的好与坏的标准即:这个接口类型的使用者是否舒服、被使用的次数多不对。 文中展示的例子是对数字文化产品(如:音乐、电影和书籍)的抽象。

其中对音乐和电影抽象出的Streamer值得思考

type Streamer interface {
    Stream() (io.ReadCloser, error)
    RunningTime() time.Duration
    Format() string
}

每个具体类型基于他们实现接口声明的所有方法隐式地实现了接口。在Go语言中我们可以在需要的时候定义一个新的抽象或者特定特点的组,而不需要修改具体类型的定义。