深入 go interface 的灵魂之处

744 阅读4分钟

概念:

接口即是一组行为的抽象与组合,即定义一些方法(行为)。在go中,interface是一种抽象类型,我们也可以称之为接口类型



type Writer interface {
    // 方法名(参数列表) 返回值列表
    Write(p []byte) (n int, err error)   
}

注意:

与其他编程语言中的接口不同,Go 中的interface是隐式实现的

type Spoker interface {
    Say()
} 

// siri 
type Siri struct {}

// xiaoai
type XiaoAi struct {}


func (s Siri) Say() {
    fmt.Println("你好,我是语音助手siri")
}

func (x XiaoAi) Say() {
    fmt.Println("我是小爱,你的小米语音助手")
}

由上面的代码可知,go中并没显式地使用Spoker接口去实现方法,就实现了接口的所有方法。同时这种方式给我们带来略微的不便,不能清楚查看哪些struct实现了哪些接口,需要借助其他工具实现查看。

接口实现

1.条件:

  1. 一个struct 要实现这个接口就必须实现该接口的全部方法。

2.接口类型变量

接口类型变量存储所有实现了该接口的实例。

func main()  {
	// 声明Spoker类型的变量rx
	var rx Spoker
	// 实例化Siri
	var s Siri = Siri{}
	// 实例化XiaoAi
	var x XiaoAi = XiaoAi{}
	// 赋值Siri实例给变量rx
	rx = s
	rx.Say()
	// 赋值XiaoAi实例给变量rx
	rx = x
	rx.Say()

}

面试题:

下面代码能否编译通过?

type People interface {
    Speak(string) string
}

type Student struct {}

func (stu *Student) Speak(str string) (ctx string) {

    if str == "xb" {
    	ctx = "学霸"
    } else {
    	ctx = "学渣"	
    }
    return
}

func main() {
    pep := Student{}
    ctx := "xz"
    var content string = pep.Speak(ctx)
    fmt.Println(content)
}

控制台输出:

// console
[Running] go run "/Users/sakurayang/unit_go/src/code.local/interface/interfaceTest.go"
学渣

[Done] exited with code=0 in 1.292 seconds

3.嵌入式接口

在go中, 接口还可以像👇的例子组装,不仅提高代码的简洁,还增强其灵活性。

package main

import "fmt"

type Eater interface {
	Eat() string
}

type Humaner interface {
	Eater
}

type Student struct {
	Name string
}

func (s *Student) Eat() string {
	return s.Name + " eating at the moment."
}

func main() {
	stu := Student{"Lily"}
	temp := stu.Eat()
	fmt.Println(temp)
}

Go 语言底层大量包就是基于接口嵌套的方式实现的,其中举个广泛使用的接口 io 里面的 ReadWriter :

// Reader is the interface that wraps the basic Read method.
type Reader interface {
    Read(p []byte) (n int, err error)
}

// Writer is the interface that wraps the basic Write method.
type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
    Reader
    Writer
}

interface类型

1.非空接口类型

通常我们实现接口时,既可以结构体类型的方法也可以是使用指针类型的方法。Go语言中并没有严格规定实现者的方法是值类型还是指针。

package main

import "fmt"

type Student struct {
	Id     int
	Name   string
	Gender bool
}

type Beaner interface {
	GetName() string
	SetName(string)
}

func (s Student) GetName() string { // 1
	return s.Name
}

func (s *Student) SetName(name string) { // 2
	s.Name = name
}

func main() {
	stu := Student{1, "lisi", true} // 3 类型:main.Student
	stu.SetName("zhangsan")
	fmt.Println(stu.GetName(), stu)
	copyObj(stu)
}

// --- console ---
[Running] go run "/Users/sakurayang/unit_go/src/code.local/interface/getterInferface.go"
zhangsan {1 zhangsan true}
{1 wanger true}

[Done] exited with code=0 in 0.808 seconds
  1. 使用结构体实现接口
  1. 使用了结构体指针实现接口
  1. 使用结构体初始化变量(赋值触发类型的转换)

当我们如下面这样编写代码时,对比上面的栗子, 思考下面的例子为什么会出现编译失败?

func main() {
	var stu Beaner = Student{1, "lisi", true}  // 
	stu.SetName("zhangsan")
	fmt.Println(stu.GetName(), stu)
}

[Running] go run "/Users/sakurayang/unit_go/src/code.local/interface/getterInferface.go"
# command-line-arguments
./getterInferface.go:3:6: cannot use Student{...} (type Student) as type Beaner in assignment:
	Student does not implement Beaner (SetName method has pointer receiver)

[Done] exited with code=2 in 0.159 seconds

结论1: go中函数都是按值传递

结论2: 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法。

package main

import "fmt"

type Eater interface {
	Eat()
    Sleep()
}

type Humaner interface {
	Eater
}

type Student struct {
	Name string
}

func (s *Student) Eat() {
    fmt.Println(s.Name + " eating at the moment.")
}

func (s *Student) Sleep() {
	fmt.Println(s.Name + " go to bed at night.")
}

func main() {
	var s Humaner = &Student{"Anna"}
	s.Eat()
	s.Sleep()
}

// ------- console -------
[Running] go run "/Users/sakurayang/unit_go/src/code.local/interface/nestedInterface.go"
Anna eating at the moment.
Anna go to bed at night.

[Done] exited with code=0 in 0.661 seconds

看一下下面这个栗子,思考为什么错误:

package main

import "fmt"

type Eater interface {
	Eat()
    Sleep()
}

type Humaner interface {
	Eater
}

type Student struct {
	Name string
}

func (s *Student) Eat() {
    fmt.Println(s.Name + " eating at the moment.")
}

// func (s *Student) Sleep() {
// 	fmt.Println(s.Name + " go to bed at night.")
// }

func main() {
	var s Humaner = &Student{"Anna"}
	s.Eat()
	//s.Sleep()
}
// ----- console ------
[Running] go run "/Users/sakurayang/unit_go/src/code.local/interface/nestedInterface.go"
# command-line-arguments
./nestedInterface.go:27:6: cannot use &Student{...} (type *Student) as type Humaner in assignment:
	*Student does not implement Humaner (missing Sleep method)

[Done] exited with code=2 in 0.229 seconds

总结

  1. go中要实现该接口,必须实现该接口所有方法名的函数方法

2.interface{} 类型(空接口)

应用场景:

1.可以接收任何类型的函数参数

func main() {
	n := "zhangsan"
	a := 13
	g := true
	convertType(n)
	convertType(a)
	convertType(g)
}

func convertType(t interface{}) {
	fmt.Printf("val:%v type:%T\n", t, t)
}

2.作为map的值,保存任意值的字典

m := make(map[string]interface{})
m["id"] = 1
m["name"] = "zhangsan"
m["gender"] = true

3.interface{} 类型不是任意类型

type TestObj struct{}

func checkNil(t interface{}) bool {
	return t == nil
}

func main() {
	var s  *TestObj
	fmt.Println(s == nil)
	fmt.Println(checkNil(s))
}

类型断言

v, ok := x.(T)

x: interface{}的变量
T: 类型

返回参数:
1. v: x转T类型后的变量;
2. ok: 布尔值, 如果为true,则为断言成功; 如果为false, 则为断言失败。

下面举个栗子:

package main

import "fmt"

func checkType(x interface{}) {

	
	
	switch v := x.(type) {
	case string: 
		fmt.Printf("the type is a string, value is %v\n", v)
	case int:
		fmt.Printf("the type is a int, value is %v\n", v)
	case bool:
		fmt.Printf("the type is a bool, value is %v\n", v)
	default:
		fmt.Printf("the unknown type.")
	}
}


func main() {
	m := "zhangsan"
	checkType(m)
	n := 123456
	checkType(n)
	k := true
	checkType(k)
}