Dynamic Types in Golang.
About 1984 wordsAbout 7 min
Golang
2024-11-13
讲述了接口和动态类型的关系。
Go
中的动态类型
在经典的面向对象语言(像 C++,Java 和 C#)中数据和方法被封装为 类
的概念:类包含它们两者,并且不能剥离。
Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。
Go 中的接口跟 Java/C# 类似:都是必须提供一个指定方法集的实现。但是更加灵活通用:任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明。
和其它语言相比,Go 是唯一结合了接口值,静态类型检查(是否该类型实现了某个接口),运行时动态转换的语言,并且不需要显式地声明类型是否满足某个接口。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。
接收一个(或多个)接口类型作为参数的函数,其实参可以是任何实现了该接口的类型的变量。 实现了某个接口的类型可以被传给任何以此接口为参数的函数
。
类似于 Python 和 Ruby 这类动态语言中的 动态类型(duck typing)
;这意味着对象可以根据提供的方法被处理(例如,作为参数传递给函数),而忽略它们的实际类型:它们能做什么比它们是什么更重要。
动态方法调用
像 Python,Ruby 这类语言,动态类型是延迟绑定的(在运行时进行):方法只是用参数和变量简单地调用,然后在运行时才解析(它们很可能有像 responds_to
这样的方法来检查对象是否可以响应某个方法,但是这也意味着更大的编码量和更多的测试工作)
Go 的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 interface{}
这样的“泛型”上,你可以通过类型断言来检查变量是否实现了相应接口。
接口提取
提取接口
是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。
Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。
所以你不用提前设计出所有的接口;整个设计可以持续演进,而不用废弃之前的决定
。类型要实现某个接口,它本身不用改变,你只需要在这个类型上实现新的方法。
函数重载
Go 语言中函数重载可以用可变参数 ...T
作为函数最后一个参数来实现。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。
函数 fmt.Printf
就是这样做的:
fmt.Printf(format string, a ...interface{}) (n int, errno error)
1这个函数通过枚举 slice
类型的实参动态确定所有参数的类型。并查看每个类型是否实现了 String()
方法,如果是就用于产生输出信息。
接口的继承
当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。
type Task struct {
Command string
*log.Logger
}
这个类型的工厂方法像这样:
func NewTask(command string, logger *log.Logger) *Task {
return &Task{command, logger}
}
当 log.Logger
实现了 Log()
方法后,Task 的实例 task 就可以调用该方法:
task.Log()
类型可以通过继承多个接口来提供像 多重继承
一样的特性:
type ReaderWriter struct {
*io.Reader
*io.Writer
}
上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少。这被认为是 Go 编程中的重要的最佳实践。
面向对象
我们总结一下前面看到的:Go
没有类,而是松耦合的类型、方法对接口的实现。
OO
语言最重要的三个方面分别是:封装,继承和多态。在 Go 中它们是怎样表现的呢?
- 封装(数据隐藏):和别的 OO 语言有 4 个或更多的访问层次相比,Go 把它简化为了 2 层):
- 包范围内的:通过标识符首字母小写,
对象
只在它所在的包内可见- 可导出的: - 通过标识符首字母大写,
对象
对所在包以外也可见
- 包范围内的:通过标识符首字母小写,
类型只拥有自己所在包中定义的方法。
- 继承:用组合实现:内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现
- 多态:用接口实现:某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。Go 接口不是 Java 和 C# 接口的变体,而且接口间是不相关的,并且是大规模编程和可适应的演进型设计的关键。
高价函数
通常你在应用中定义了一个结构体,那么你也可能需要这个结构体的(指针)对象集合,比如:
type Any interface{}
type Car struct {
Model string
Manufacturer string
BuildYear int
// ...
}
type Cars []*Car
然后我们就可以使用高阶函数,实际上也就是把函数作为定义所需方法(其他函数)的参数。
// cars.go
package main
import (
"fmt"
)
// 定义一个空接口 Any,表示可以是任意类型
type Any interface{}
// 定义一个结构体 Car,表示汽车的属性
type Car struct {
Model string // 车型
Manufacturer string // 制造商
BuildYear int // 生产年份
// ...
}
// 定义一个类型 Cars,表示指向 Car 的指针切片
type Cars []*Car
func main() {
// 创建一些汽车对象
ford := &Car{"Fiesta", "Ford", 2008}
bmw := &Car{"XL 450", "BMW", 2011}
merc := &Car{"D600", "Mercedes", 2009}
bmw2 := &Car{"X 800", "BMW", 2008}
// 查询操作
allCars := Cars([]*Car{ford, bmw, merc, bmw2})
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
fmt.Println("AllCars: ", allCars)
fmt.Println("New BMWs: ", allNewBMWs)
// 定义制造商列表
manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
allCars.Process(sortedAppender)
fmt.Println("Map sortedCars: ", sortedCars)
BMWCount := len(sortedCars["BMW"])
fmt.Println("We have ", BMWCount, " BMWs")
}
// 用给定的函数 f 处理所有汽车
func (cs Cars) Process(f func(car *Car)) {
for _, c := range cs {
f(c)
}
}
// 查找所有满足给定条件的汽车
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
cars := make([]*Car, 0)
cs.Process(func(c *Car) {
if f(c) {
cars = append(cars, c)
}
})
return cars
}
// 处理汽车并创建新的数据
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, len(cs))
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
// 创建一个函数,用于将汽车分类到指定的制造商中
func MakeSortedAppender(manufacturers []string) (func(car *Car), map[string]Cars) {
// 准备一个存储排序后的汽车的映射
sortedCars := make(map[string]Cars)
for _, m := range manufacturers {
sortedCars[m] = make([]*Car, 0)
}
sortedCars["Default"] = make([]*Car, 0)
// 准备添加器函数:
appender := func(c *Car) {
if _, ok := sortedCars[c.Manufacturer]; ok {
sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
} else {
sortedCars["Default"] = append(sortedCars["Default"], c)
}
}
return appender, sortedCars
}