云原生探索系列(十二):Go 语言接口详解

前言

Go 语言中的接口是其核心特性之一, Go 的接口不像许多面向对象语言中的接口那样需要显式地声明"实现"某个接口, 而是通过 隐式实现 来实现接口的契约。这篇文章就深入探讨一下 Go 的接口类型。

1、接口的基础概念

1.1 接口的定义

接口是一组方法的集合,一个类型实现了接口所要求的所有方法,就自动实现了该接口,无需显式声明。 感觉和 Python 的灵活的子类化机制有点类似。

接口的定义形式如下:

scss 复制代码
type InterfaceName interface {
    Method1() returnType
    Method2() returnType
    // ...
}

Go 中的接口和其他语言的接口有所不同,最主要的特点是接口的 隐式实现。 即使没有在结构体中声明"实现了某个接口",只要结构体实现了接口中的所有方法,那么它就自动实现了该接口。

1.2 接口的基本应用

看下面这个例子:

go 复制代码
type Pet interface {
	Name() string
	Category() string
}

type Dog struct {
	name string
}

func (dog Dog) Name() string {
	return dog.name
}

func (dog Dog) Category() string {
	return "dog"
}

func main() {
	dog := Dog{"little pig"}
	fmt.Printf("The dog's name is %q.\n", dog.Name())  // The dog's name is "little pig".
	var pet Pet = dog
	fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())  // This pet is a dog, the name is "little pig".
}

代码解析:

  • 在上面的代码中, Pet 是一个接口,包含了两个个方法 NameCategory
  • Dog 类型实现了 NameCategory 方法,自动满足了 Pet 接口,因此可以声明并初始化一个 Dog 类型的变量 dog, 并把这个变量赋给一个 Pet 类型的变量 pet
  • 虽然Dog 没有显式声明"实现了 Pet 接口,而是因为实现了接口中的方法,因此自动符合接口。

2、空接口

Go 语言中有一个非常重要的接口类型------空接口 (interface{})。 空接口可以存储任何类型的值,因为空接口没有方法,也就没有任何的限制。因此,空接口通常用于存储不确定类型的数据。

2.1 空接口的使用

scss 复制代码
func printAnything(i interface{}) {
    fmt.Println(i)
}

func main() {
    printAnything(42)        // 输出:42
    printAnything("Hello")   // 输出:Hello
    printAnything(3.14)      // 输出:3.14
}

代码解析:

  • printAnything 是一个函数,接受一个参数 i ,它的类型是 interface{} ,即空接口。
  • main 函数中,分别传入整数值、字符串值、浮点数值进行测试

2.2 类型断言

虽然空接口可以接受任何类型的值,但有时我们需要从空接口中恢复出具体的类型,这时我们就需要使用类型断言。

go 复制代码
package main

import "fmt"

func assertType(i interface{}) {
    v, ok := i.(int)  // 类型断言,检查 i 是否是 int 类型
    if ok {
        fmt.Println("Integer value:", v)
    } else {
        fmt.Println("Not an integer!")
    }
}

func main() {
    assertType(42)      // 输出:Integer value: 42
    assertType("Hello") // 输出:Not an integer!
}

在这个例子中, i.(int) 是类型断言,它判断 i 是否是 int 类型,并将其转换为 int 类型。 如果不是 int 类型,断言会失败。

3、接口和多态

接口是 Go 语言实现多态的关键。通过接口,一个函数可以接受不同类型的参数,而无需关心这些类型的具体实现。

3.1 多态的示例

1.2 模块中代码进行改造,如下:

go 复制代码
type Pet interface {
	Name() string
	Category() string
}

type Dog struct {
	name string
}

type Cat struct {
	name string
}

func (dog Dog) Name() string {
	return dog.name
}

func (dog Dog) Category() string {
	return "dog"
}

func (dog Cat) Name() string {
	return dog.name
}

func (dog Cat) Category() string {
	return "cat"
}

func PrintPet(pet Pet) {
	fmt.Printf("This pet is a %s, the name is %q.\n", pet.Category(), pet.Name())
}

func main() {
	dog := Dog{"tom-dog"}
	cat := Cat{"tom-cat"}
	PrintPet(dog)  // This pet is a dog, the name is "tom-dog".
	PrintPet(cat)  // This pet is a cat, the name is "tom-cat".
}

代码解析:

  • DogCat 都实现了 NameCategory 方法,因而都满足了 Pet 接口。
  • PrintPet 函数接受 Pet 类型的参数,并可以接受任何实现了 Pet 接口的类型。 这样,函数就表现出多态的行为。

4、接口类型的零值和 nil

在 Go 中,接口类型的零值是 nil ,这意味着接口变量可以是 nil ,表示它既没有绑定类型,也没有具体的值。

4.1 接口零值的示例

go 复制代码
type Pet interface {
	Name() string
}

type Dog struct {
	name string
}

func (d Dog) Name() string {
	return d.name
}

func main() {
	var pet Pet
	fmt.Println(pet == nil)  // true

	var dog *Dog
	fmt.Println(dog == nil) // true

	pet = dog
	fmt.Println(pet == nil)  // false
}

代码解析:

  • 接口变量 pet 默认值为 nil ,即它没有绑定任何具体类型。
  • 如果接口变量 pet 指向一个实现了接口的类型,那么 pet 就不再是 nil

4.2 深入理解为啥dog == nil ,赋值给 pet, pet不为nil?

先理解两个名词:动态值 和 动态类型。

上面代码中,我们把指针变量 dog 的值赋给变量 pet ,这个结果值就是变量 pet 的动态值,而结果值的类型 *Dog 就是 变量 pet 的动态类型。对于变量 pet来讲,它的静态类型永远是 Pet ,但它的动态类型是随着赋给它的值而变化的。

当我们把 dog 的值赋值给变量 pet 时, dog 的值会先被复制,这里是 nil, 没必要复制。然后,Go 会使用专用 数据结构 iface 的实例包装这个dog 的值的副本,虽然被包装的动态值是 nil ,但 pet 的值不会是 nil ,因为 这个动态值只是 pet 值的一部分而已, pet 的动态类型已经存在了,就是 *dog.

我们可以使用 reflect 包来验证一下:

css 复制代码
t := reflect.TypeOf(pet)
fmt.Println("type:", t) // type: *main.Dog

v := reflect.ValueOf(pet)
fmt.Println("value:", v)  // value: <nil>

最后

这篇文章主要阐述了 Go 语言接口类型的几个特点:隐式实现、多态、空接口等,另外深入探讨了接口变量赋值过程发生了什么,也就是 4.2中的内容。 接口的这些特性使得 Go 语言在处理多态和可扩展性方面非常强大,能够支持各种灵活的设计模式。

相关推荐
来自上海的这位朋友5 分钟前
Spring Boot + MySQL 搭一个多人游戏后端:登录、房间、匹配、对局和成长系统
前端·后端·three.js
来自上海的这位朋友6 分钟前
浏览器里的实时对局同步:WildHunt 的 WebSocket、输入序号与服务端快照
前端·javascript·后端
用户398346161208 分钟前
Go-Spring 实战第 14 课 —— Bean 注册函数:Provide、Module、Group 以及 Configuration
spring·go
chasdream9 分钟前
Doris批量导入慢?Spring Boot整合Doris Routine Load是如何提升数据导入性能
后端·数据分析
今晚务必早点睡10 分钟前
2026 最新互联网架构演进:从“云原生”走向“AI 原生”
人工智能·云原生·架构
用户21816970493010 分钟前
golang 并发 goroutine sync.Lock atomic WaitGroup 协程通信(共享数据,channqel消息)channel
后端
Reart12 分钟前
从0解构tinyweb项目(十三)--剩余Handler自读验证(未完成版)
后端
蜀道山老天师16 分钟前
Docker 进阶:数据持久化与容器网络互联(数据卷、挂载目录、端口映射、自定义网络)
运维·网络·docker·云原生·容器
Gopher_HBo29 分钟前
接入层Nginx
后端
IT_陈寒36 分钟前
Vite热更新把我整不会了,原来还要这样配!
前端·人工智能·后端