云原生探索系列(十二):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 语言在处理多态和可扩展性方面非常强大,能够支持各种灵活的设计模式。

相关推荐
无忧无虑Coding3 小时前
pyinstall 打包Django程序
后端·python·django
求积分不加C4 小时前
Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程
java·spring boot·后端
枫叶_v4 小时前
【SpringBoot】26 实体映射工具(MapStruct)
java·spring boot·后端
2401_857617625 小时前
汽车资讯新趋势:Spring Boot技术解读
java·spring boot·后端
小林学习编程6 小时前
从零开始理解Spring Security的认证与授权
java·后端·spring
写bug的羊羊6 小时前
Spring Boot整合Nacos启动时 Failed to rename context [nacos] as [xxx]
java·spring boot·后端
晴子呀6 小时前
微服务系列概览
微服务·云原生·架构
努力的小陈^O^6 小时前
docker学习笔记跟常用命令总结
java·笔记·docker·云原生
2402_857589367 小时前
实验室管理效率提升:Spring Boot技术的力量
java·spring boot·后端
2401_857636397 小时前
Spring Boot图书馆管理系统:疫情中的技术实现
java·spring boot·后端