跳到主要内容

Go 鸭子类型和类型约束

鸭子类型

鸭子类型是一种编程概念,主要用于像 Python 这样的动态语言。它基于这样的理念:如果一个对象拥有某种行为和属性,不管它属于什么类,都可以当作是那种类型。在这种类型系统中,对象的实际类型并不重要,重要的是它是否实现了特定的方法或属性。因此,我可以编写一个函数,只要对象具有需要的方法,不管它的具体类型,这个函数都可以使用它。

JavaScript 示例

interface Quackable {
quack(): void;
}

class Duck implements Quackable {
quack() {
console.log("Quack, quack!");
}
}

class Person {
quack() {
console.log("我在模仿鸭子叫!");
}
}

function makeItQuack(ducky: Quackable) {
ducky.quack();
}

const duck = new Duck();
const person = new Person();

makeItQuack(duck); // 输出:Quack, quack!
makeItQuack(person); // 输出:我在模仿鸭子叫!

在这个示例中,makeItQuack 函数接受任何实现了 Quackable 接口的对象。不论对象的实际类型,只要具有 quack 方法,都可以传入该函数。

Go 语言示例

Go 语言的“解耦”主要体现在接口 Duck 和其实现者 Bird 之间的关系。Bird 结构体不需要显式声明实现了 Duck 接口。只要 Bird 提供了 Duck 接口要求的所有方法,它就被视为实现了该接口。这体现了鸭子类型的思想,因为实现者(Bird)和接口(Duck)之间没有直接的、显式的关联。

package main

func main() {
bird := &Bird{
legs: 2,
}
makeItWalkAndShout(bird)
}

type Duck interface {
walk()
shout()
}

type Bird struct {
legs int
}

func (b *Bird) walk() {
println("walk")
}

func (b *Bird) shout() {
println("shout")
}

func makeItWalkAndShout(animal Duck) {
animal.walk()
animal.shout()
}

在这个 Go 示例中,makeItWalkAndShout 函数接受任何实现了 Duck 接口的类型。Bird 结构体虽然没有显式声明实现 Duck 接口,但由于包含了 walkshout 方法,所以满足了接口要求,可以传入 makeItWalkAndShout 函数。

any 类型

在 Go 语言中,anyinterface{} 的别名。

package main

import "fmt"

func main() {
student := map[string]any{
"name": "张三",
"age": 18,
}

fmt.Println(student)
}

在这个示例中,我使用了 any 类型来表示可以存储任意类型的值,方便在集合中混合不同类型的数据。

类型参数的约束

在 Go 的泛型中,可以使用接口来约束类型参数,指定允许的类型范围。

type Numeric interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32
}

func add[T Numeric](a, b T) T {
return a + b
}

这里定义了一个接口 Numeric,包含了多种整数类型。泛型函数 add 通过类型参数 [T Numeric] 限制了参数 ab 必须是 Numeric 类型,实现了对不同整数类型的加法运算。

自定义类型

可以基于已有的类型定义新的自定义类型。

type MyInt int

MyInt 是一个新的类型,底层类型为 int

类型集

使用类型集可以创建泛型函数,接受任何底层类型为指定类型的自定义类型。

package main

import "fmt"

type MyInt int

// 接受任何底层类型为 int 的类型
func increment[T ~int](value T) T {
return value + 1
}

func main() {
var num int = 5
var myNum MyInt = 6

fmt.Println(increment(num)) // 输出:6
fmt.Println(increment(myNum)) // 输出:7
}

在这个示例中,类型参数 [T ~int] 表示 T 可以是 int 或者任何底层类型为 int 的类型,例如自定义类型 MyInt

注意事项

  • 类型断言的使用:在处理接口类型时,可以使用类型断言将其转换为具体类型,但需要注意断言失败的情况,避免引发运行时错误。

  • 泛型的版本要求:泛型功能在 Go 1.18 及以上版本引入,使用前需确保 Go 版本符合要求。

  • 变量命名:应使用易读的变量名,提升代码可读性。

  • 接口的用途:接口用于定义一组方法,规定类型需要实现哪些方法,实现了接口的类型可以被当作接口类型使用。

  • 接口的嵌套:可以通过接口嵌套来组合多个接口,使得新接口包含所有嵌套接口的方法。