问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Go语言中多态的实现方式详解

创作时间:
作者:
@小白创作中心

Go语言中多态的实现方式详解

引用
CSDN
1.
https://blog.csdn.net/slnsuya/article/details/137452089

在面向对象编程中,多态是一个核心概念,它允许一个接口被多种类型实现,从而实现代码的复用和灵活性。Go语言虽然没有显式的类和继承机制,但通过接口实现了多态性。本文将深入探讨Go语言中多态的实现方式,包括接口的定义、实现、类型检查机制以及指针和接口的使用。

多态的定义

多态(Polymorphism)指的是同一个方法调用可以根据对象的不同类型而具有不同的行为。简而言之,多态允许同样的方法在不同的对象上表现出不同的行为。

在计算机科学中,接口是计算机系统中多个组件共享的边界,不同的组件能够在边界上交换信息。接口的本质是引入一个新的中间层,调用方可以通过接口与具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口。

Go中多态的实现

在Go语言中,多态是通过接口实现的。与Java不同的是,Go语言中接口的实现是隐式的。当一个类型实现了接口中的所有方法时,它就被认为是实现了该接口。

下面是一个简单的例子:

package main
import "fmt"

// 定义一个接口 Animal
type Animal interface {
    Speak() string
}

// 定义结构体 Dog
type Dog struct{}

// Dog 实现了接口 Animal 的 Speak 方法
func (d Dog) Speak() string {
    return "Woof!"
}

// 定义结构体 Cat
type Cat struct{}

// Cat 实现了接口 Animal 的 Speak 方法
func (c Cat) Speak() string {
    return "Meow!"
}

// 接收一个 Animal 类型的参数,并调用其 Speak 方法
func LetAnimalSpeak(a Animal) {
    fmt.Println(a.Speak())
}

func main() {
    // 创建一个 Dog 实例
    dog := Dog{}
    // 创建一个 Cat 实例
    cat := Cat{}
    // 调用 LetAnimalSpeak 函数,传入不同类型的参数
    LetAnimalSpeak(dog) // 输出: Woof!
    LetAnimalSpeak(cat) // 输出: Meow!
}

需要注意的是,如果一个类型只实现了接口中的一部分方法,那么它不会被认为是实现了该接口。例如:

package main
import "fmt"

type Animal interface {
    Speak() string
    Run() string
}

type Cat struct{}

func (c Cat) Speak() string {
    return "Meow!"
}

func main() {
    cat := Cat{}
    var animal Animal = cat // 编译错误: Cat does not implement Animal (missing Run method)
    fmt.Println(animal.Speak())
    fmt.Println(animal.Run())
}

Go语言进行接口类型检查的机制

Go语言在编译期间会对代码进行类型检查。当一个值被赋值给接口变量、传递给函数参数或从函数返回时,编译器会检查该值是否实现了接口中定义的所有方法。

下面是一个示例:

func main() {
    var rpcErr error = NewRPCError(400, "unknown err") // typecheck1
    err := AsErr(rpcErr) // typecheck2
    println(err)
}

func NewRPCError(code int64, msg string) error {
    return &RPCError{ // typecheck3
        Code:    code,
        Message: msg,
    }
}

func AsErr(err error) error {
    return err
}

在这个例子中,编译器会在三个地方进行类型检查:

  1. *RPCError 类型的变量赋值给 error 类型的变量 rpcErr
  2. *RPCError 类型的变量 rpcErr 传递给签名中参数类型为 errorAsErr 函数
  3. *RPCError 类型的变量从函数签名的返回值类型为 errorNewRPCError 函数中返回

Go语言接口的具体类型

Go语言中有两种类型的接口:一种是带有一组方法的接口,另一种是不带任何方法的 interface{}

eface

eface 用于表示没有方法的空接口(interface{})类型的变量。例如:

func DoSomething(v interface{}) {
    // ...
}

DoSomething 函数内部,v 的类型是 interface{},而不是任意类型。所有值在运行时都会被转换为 interface{} 类型的值。

eface 的内存结构如下:

type eface struct { // 16 字节
    _type *_type // 指向数据类型的具体类型信息
    data  unsafe.Pointer // 指向底层数据
}

iface

iface 用于表示拥有方法的接口类型变量。其内存结构如下:

type iface struct { // 16 字节
    tab  *itab
    data unsafe.Pointer
}

其中,itab 结构体包含接口类型的核心信息:

type itab struct { // 32 字节
    inter *interfacetype // 8字节存储接口本身的信息
    _type *_type // 8字节具体的具体类型信息
    hash  uint32 // 4字节, 字段是_type.hash的缓存,当需要将接口类型转换成具体的类型时,使用该字段判断转换的目标类型是否和具体类型_type一样
    _     [4]byte // 4字节
    fun   [1]uintptr // 8字节存储一组函数指针,是一个用于动态分发的虚函数表
}

指针和接口

在Go语言中,同时使用指针和接口时可能会遇到一些困惑。接口在定义一组方法时没有对实现的接收者做限制,因此可以有以下两种实现方式:

  1. 使用结构体实现接口
  2. 使用结构体指针实现接口

例如:

type Cat struct {}
type Duck interface { ... }
func (c  Cat) Quack {}  // 使用结构体实现接口
func (c *Cat) Quack {}  // 使用结构体指针实现接口
var d Duck = Cat{}      // 使用结构体初始化变量
var d Duck = &Cat{}     // 使用结构体指针初始化变量

四种组合中只有使用指针实现接口,使用结构体初始化变量无法通过编译,其他的三种情况都可以正常执行。

  • 结构体实现接口,结构体初始化变量:通过
  • 结构体实现接口,结构体指针初始化变量:通过
  • 结构体指针实现接口,结构体初始化变量:不通过
  • 结构体指针实现接口,结构体指针初始化变量:通过

这种设计的原因在于Go语言在传递参数时都是传值的。当使用指针实现接口时,只有指针类型的变量才会实现该接口;当我们使用结构体实现接口时,指针类型和结构体类型都会实现该接口。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号