likes
comments
collection
share

全网注释第二全的GO教程-接口(Interface)

作者站长头像
站长
· 阅读数 19

持续更新: github.com/Zhouchaowen… 感谢 star

Interface

在电脑主板上,有不同的接口(例如USBHDMI、音频接口等),它们可以用来连接不同的外部设备(例如鼠标、键盘、显示器、扬声器等)。这些接口提供了一种标准的协议,允许不同的设备进行通信和交互。

全网注释第二全的GO教程-接口(Interface)

Golang中,interface的作用就类似于电脑主板上的接口。它是一种类型,用于定义一组方法签名,一个实现了这组方法的具体类型被称为这个interface实现类型。就像USB接口一样, USB定义了插口大小, 金属引脚数量等; 任何品牌的鼠标,键盘等外部组件只要按照USB规定的插口大小,引脚数量进行制作,就能插到USB接口上并使用。

interface类型是一种抽象的类型,它不会暴露出所包含的具体值的内部结构和数据。同时interface 类型可以代表任意类型的值,因此它可以用来定义不同类型的值。

目录

  • 接口定义与实现
  • 接口应用举例
  • 接口断言

接口定义与实现

定义接口语法格式:

type interfaceName interface {
	functionName() // 方法组
    ......
  // functionName2(type) type
}

typeinterface为关键字, interfaceName为接口名称, functionName()为接口方法组中的一个方法名称, 举例如下:

// 定义了一个名称为 Duck 的接口
type Duck interface {
	GaGaga() // 只包含一个方法 GaGaga()  
}

实现接口:必须要实现接口的所有方法才能被叫做实现该接口

type BlackSwan struct {
	Name  string
	Color string
}

// BlackSwan 实现了GaGaga()方法
func (d BlackSwan) GaGaga() {
	fmt.Printf("%s, ga ga ga\n", d.Name)
}

调用接口方法

func main() {
	var d Duck // 定义了一个 Duck 接口名称为d

	d = BlackSwan{ // 将定义的BlackSwan实例赋值给d,因为BlackSwan实现了Duck接口
		Name:  "黑天鹅",
		Color: "黑色",
	}
	d.GaGaga() // 调用接口定义的方法,且只能调用定义过的方法
}

定义并实现一个接口:

package main

import "fmt"

/*
	1.通过接口定义方法
	2.实现接口定义方法
*/

// Duck 接口类型 定义一组方法签名的集合
type Duck interface {
	GaGaga() // 定义接口约定
	// ....
}

// 定义一个新类型(相当于给string起了个别名)
type DonaldDuck string

// DonaldDuck 实现了GaGaga()函数
func (d DonaldDuck) GaGaga() {
	fmt.Printf("%s, ga ga ga\n", d)
}

type RubberDuck string

// RubberDuck 实现了GaGaga()函数
func (d RubberDuck) GaGaga() {
	fmt.Printf("%s, ga ga ga\n", d)
}

type BlackSwan struct {
	Name  string
	Color string
}

// BlackSwan 实现了GaGaga()函数
func (d BlackSwan) GaGaga() {
	fmt.Printf("%s, ga ga ga\n", d.Name)
}

func main() {
	var d Duck

	// 可以将DonaldDuck具体类型赋值给接口Duck类型,因为实现了接口类型的方法集合
	d = DonaldDuck("🦆 唐老鸭")
	d.GaGaga()

	d = RubberDuck("🦆 小黄鸭")
	d.GaGaga()

	d = BlackSwan{
		Name:  "黑天鹅",
		Color: "黑色",
	}
	d.GaGaga()
}

以上代码定义了一个接口类型 Duck,并定义了三个实现该接口的具体类型 DonaldDuckRubberDuckBlackSwan

其中,DonaldDuckRubberDuck 都实现了 GaGaga() 方法,可以输出 xxx, ga ga ga 的字符串,而 BlackSwan 类型也实现了 GaGaga() 方法,输出 xxx, ga ga ga

然后,在 main 函数中,定义了一个 Duck 类型的变量 d,并将 DonaldDuckRubberDuckBlackSwan 类型的变量赋值给 d,这是因为这三个类型都实现了 Duck 接口,所以可以赋值给 Duck 类型的变量。

然后分别调用 dGaGaga() 方法,根据不同的类型,输出不同的字符串。

package main

import "fmt"


// Duck 接口类型 定义一组方法签名的集合
type Duck interface {
	GaGaga() // 定义接口约定
	// ....
}

type Dog struct {
	Name string
}

// Dog 实现了GaGaga()函数
func (d Dog) GaGaga() {
	fmt.Printf("%s, ga ga ga\n", d.Name)
}

func (d Dog) WangWangWang() {
	fmt.Printf("%s, wang wang wang\n", d.Name)
}

func main() {
	var d Duck

  // 接口与具体实现类调用时的对比
	d = Dog{
		Name: "小狗",
	}
	d.GaGaga()

	dog := Dog{
		Name: "哈士奇",
	}
	dog.GaGaga()
	dog.WangWangWang()
	fmt.Println(dog.Name)
}

总结: 接口类型通常用于将具体类型的实现细节与实现类型的名称分离开。这种机制提供了非常强大的面向对象编程能力,使得Go语言的面向对象编程变得更加自然和简单。它可以帮助我们构建高度抽象的代码,使代码更加灵活、易于维护和扩展。

接口应用举例

package main

import (
	"fmt"
	"math/rand"
	"time"
)

// Hero 定义一个英雄接口,包含:
// 1.释放技能方法 Skills
// 2.添加装备方法 AddEquipments
// 3.上下左右移动方法 Move
type Hero interface {
	Skills(index int)
	AddEquipments(eq string)
	Move(direction string)
}

// Houyi 英雄后裔实现 Hero 接口
type Houyi struct {
	Equipments []string
}

func (h Houyi) Skills(index int) {
	fmt.Printf("\t 释放技能 %d\n", index)
}

func (h Houyi) AddEquipments(eq string) {
	h.Equipments = append(h.Equipments, eq)
	fmt.Printf("\t 添加装备 %s\n", eq)
}

func (h Houyi) Move(direction string) {
	fmt.Printf("\t 向 %s 移动\n", direction)
}

var move = []string{"上", "下", "左", "右"}
var equipments = []string{"斗篷", "电刀", "黑切", "破军"}
var skills = []int{1, 2, 3, 4}

// operation 操作者(玩家)
// 注意operation() 接收的是 Hero 接口,这是非常重要的,这也是接口的最重要的应用
func operation(h Hero) {
	fmt.Println("开始王者操作:")
	rand.Seed(time.Now().UnixNano())
	for i := 0; ; i++ {
		tmp := i % 4
		switch tmp {
		case 0:
			m := move[rand.Intn(len(move)-1)]
			h.Move(m)
		case 1:
			s := skills[rand.Intn(len(skills)-1)]
			h.Skills(s)
		case 2:
			e := equipments[rand.Intn(len(equipments)-1)]
			h.AddEquipments(e)
		}
		time.Sleep(2 * time.Second)
	}
}

func main() {
	var hy = Houyi{}
	operation(hy)
}

如上代码中,我们定义了一个结构体Houyi,该结构体实现了Hero这个接口;我们还定义了func operation(h Hero)函数,这个函数接收一个Hero的接口类型参数,因此只要实现了Hero接口的类型就可以传递给该函数。所以我们可以将Houyi的实例hy传递给该函数。

接口断言

接口断言是指从一个接口类型中提取出具体的值和类型信息的操作。在 Golang 中,接口断言可以使用类型断言的方式进行实现。如果一个接口变量 x 的底层类型是 T 类型,我们可以使用 x.(T) 的方式对其进行类型断言,其中 .(T) 表示将 x 转换为 T 类型。

package main

import "fmt"

// 类型断言
// 断言 interface
func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	// 类型断言, 断言失败一般会导致panic的发生, 所以为了防止panic的发生, 我们需要在断言时进行一定的判断。
	// 如果断言失败, 那么ok的值将会是false
	// 如果断言成功, 那么ok的值将会是true, 同时s将会得到正确类型的值。
	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // 如果断言失败 报错(panic)
	fmt.Println(f)
}

在代码中,变量 i 的类型为 interface{},表示它可以保存任何类型的值。然后使用 .(string) 进行类型断言,将其转换为字符串类型,并将结果赋值给变量 s

接下来使用类型断言和布尔值的组合形式,将 i 断言为字符串类型,并将结果分别赋值给 sok。由于 i 的实际类型是字符串类型,因此断言成功,ok 的值为 trues 得到了正确类型的值。

然后尝试将 i 断言为 float64 类型,由于实际类型是字符串类型,这次断言失败,ok 的值为 falsef 的值为 0

最后尝试将 i 直接断言为 float64 类型,由于实际类型是字符串类型,并且没有接收断言结果,所以这次断言会导致 panic 异常的发生

为什么需要接口

  • 接口允许 Go 具有多态性, 在需要多态性的 Go 中使用接口。

  • 在可以传递多种类型的函数中,可以使用接口。

  • 接口还用于帮助减少重复/样板代码。

在需要动态类型参数的函数和方法的情况下,接口非常有用,例如接受任何类型值的 Println 函数。

思考题

自检

  • interface的定义和声明 ?
  • interface的类型断言 ?
  • interface空的使用 ?
  • interface的比较 ?
  • interface的底层原理 ?
  • interface的实现方式 ?

参考

blog.knoldus.com/how-to-use-…

stackoverflow.com/questions/3…

stackoverflow.com/questions/2…

blog.boot.dev/golang/gola…

golangbyexample.com/interface-i…

betterprogramming.pub/a-comprehen…

持续更新: github.com/Zhouchaowen… 感谢 star

交流群

微信搜索【面试情报局】回复:交流群