Golang入门学习之接口(interface)--练气十层

946 阅读6分钟

写在前面

在上一篇文章《Golang入门学习之方法(method)》当中,我们学习了Golang当中的方法的定义与运用,在接下来的这篇文章当中,我们将一起来学习Goalng的接口(interface)。

接口的定义

接口是定义了一组需要被实现的方法的抽象类型,实现接口的数据类型可以视为接口的实例。接口由一组方法与一个接口类型组成。声明格式如下:

type 接口名 interface{
    方法名(形参列表) (返回值列表)
    ...
}

按照Golang的编程风格(约定),只包含一个方法的接口的名字由方法名加 [e]r 后缀组成,例如 PrinterReaderWriterLoggerConverter 等等。还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 Java 中那样)。

Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。

请看下面这个例子:

首先,我们在interfaces包中声明一个SpiritualRootAble(表示具备修行的能力,现了灵根接口的凡人即可修炼)接口

src/go_code/interface/interfaces/spiritual_root.go

package interfaces

// 灵根接口,实现了灵根接口的凡人即可修炼
type SpiritualRootAble interface {

	// 生成灵根
	GenSpiritualRootNames() string

	// 获取生成的灵根
	SpiritualRoot() string

	// 修行方法
	Practice()
}

其次,我们在model包当中声明一个凡人结构体(mortal) 并实现SpiritualRootAble接口

src/go_code/interface/model/mortal.go

package model

import (
	"crypto/rand"
	"fmt"
	"math/big"
)

// 凡人
type Mortal struct {
	name ,
	gender ,
	spiritualRoot string
	age int

}

func NewMortal(name, gender string, age int) Mortal {
	mortal:=Mortal{
		name:   name,
		gender: gender,
		age:    age,
	}
	mortal.spiritualRoot = mortal.GenSpiritualRootNames()
	return mortal
}

func (recv Mortal) SpiritualRoot() string {
	if &recv.spiritualRoot == nil{
		return "没有灵根"
	}

	return recv.spiritualRoot
}

func (recv Mortal)Practice()  {
	fmt.Println(recv.name,"开始修行...")
}

func (recv Mortal) GenSpiritualRootNames() string{
	gsrn := []string{
		"金灵根","水灵根","木灵根","火灵根","土灵根","没有灵根",
	}
	index, _ := rand.Int(rand.Reader, big.NewInt(5))

	return  gsrn[index.Int64()]
}

最后,我们在main中使用他们

src/go_code/interface/main/main.go

package main

import (
	"fmt"
	"go_code/interface/interfaces"
	"go_code/interface/model"
)

func main() {

	 // 声明了一个SpiritualRootAble接口类型的变量
	 var sr interfaces.SpiritualRootAble

	 // 降生了一个凡人
	 mortal := model.NewMortal("韩立","男性",1)

	 // 接口变量指向凡人实例
	sr = mortal

	// 获取凡人的灵根
	fmt.Println(sr.SpiritualRoot())

	// 凡人开始修炼
	sr.Practice()

}

输出:

火灵根
韩立 开始修行...

可以看到:

在Golang中并需要显示地声明类型实现了某一接口(不需要如同Java那样class implemments interfacesName),只要类型实现了接口当中定义的方法集,类型即是实现了该接口。

实现某个接口的类型(除了实现接口方法外)可以有其他的方法。

一个类型可以实现多个接口(实际上mortal还实现了空接口,关于空接口的内容请继续往下看)。

接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。

空接口

空接口即为不包含任何方法的接口。任何类型都实现了空接口。空接口有点类似于Java当中的Object的概念

type Any interface {}

接口嵌套接口

一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。

比如接口 File 包含了 ReadWriteLock 的所有方法,它还额外有一个 Close() 方法。

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}
type Lock interface {
    Lock()
    Unlock()
}
type File interface {
    ReadWrite
    Lock
    Close()
}

Golang中的类型断言

对于一个接口类型变量varI中可以包含任何类型的值,so,必须有一种方式来检测它的动态类型,也就是运行时变量var中存储的值的实际类型。而这,就是类型断言

v := varI.(T)

类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:

if v,ok :=varI.(T);ok{
    do something
}

如果转换合法,vvarI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,okfalse,也没有运行时错误发生。

如果我们只是需要判断varI是否为T类型而不需要获取类型T的值时,可以这样做:

if _,ok := varI.(T);ok{
    
}

我们继续沿用上面的例子讲类型断言这部分的内容

package main

import (
	"fmt"
	"go_code/interface/interfaces"
	"go_code/interface/model"
)

func main() {

	 // 声明了一个SpiritualRootAble接口类型的变量
	 var sr interfaces.SpiritualRootAble

	 // 降生了一个凡人
	 mortal := model.NewMortal("韩立","男性",1)

	 // 接口变量指向凡人实例
	sr = mortal

    // 类型断言
	if v,ok :=sr.(model.Mortal);ok{
		fmt.Println(v)
	}

}

输出:

&{韩立 男性 金灵根 1}

值得注意的是,我们在实现SpiritualRootAble 时方法的receiver类型为*Mortal ,即凡人的指针类型。因此实际上实现SpiritualRootAble接口的是*Mortal,因此在进行类型断言时T为*Mortal 。我们在使用类型断言的时候要注意这一点,不然编译器会报错。

类型断言的应用

在Golang中,我们如何测试一个值是否实现了某一接口呢?答案就是通过类型断言

var m interfaceTypeName
if _,ok := m.(interfaceTypeName);ok {
	fmt.Println(ok)
}

总结

接口可以理解为一种契约,实现类型必须满足它,它描述了类型的行为,规定类型可以做什么。接口彻底将类型能做什么,以及如何做分离开来,使得相同接口的变量在不同的时刻表现出不同的行为,这就是多态的本质。

在golang中:

  • 指针方法可以通过指针调用
  • 值方法可以通过值调用
  • 接收者是值的方法可以通过指针调用,因为指针会首先被解引用
  • 接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址

将一个值赋值给一个接口时,编译器会确保所有可能的接口方法都可以在此值上被调用,因此不正确的赋值在编译期就会失败。

写在最后

关于Golang当中接口的知识点我就简单介绍到这里。本文当中涉及到的例子可以点击此处下载。如果我的学习笔记能够给你带来帮助,还请多多点赞鼓励。文章如有错漏之处还请各位小伙伴帮忙斧正。从下一篇文章开始,我们将开启筑基系列的学习,具体涉及到的知识点有:反射、文件操作、数据交换、错误处理、Go协程(goroutine)和通道(channel)等内容。欢迎各位小伙伴订阅我的博客👊。