面向对象编程(OOP)中三个基本特征分别是封装,继承,多态。在 Go 语言中封装和继承是通过 struct
来实现的,而多态则是通过接口 interface
来实现的。
鸭子类型
在 Go 语言的类型系统中有一个核心概念:我们不应该根据类型可以容纳哪种数据而是应该根据类型可以执行哪种操作来设计抽象类型,这与鸭子模型有密切关系。
鸭子类型(duck typing)是一种程序设计风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由“当前方法和属性的集合”决定。“鸭子测试”可以这样表述:
一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为“鸭子”
鸭子类型中,关注点在于对象的行为,能作什么,而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为“鸭子”的对象,并调用它的“走”和“叫”方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的“走”和“叫”方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的“走”和“叫”方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
什么是 Go 接口
同鸭子类型,在 Go 语言中接口interface
是一种来定义对象行为(操作、方法)的抽象类型。我们可以从几个方面来理解 Go 语言中的接口:
- 它是方法的集合,它不能包含变量;
- 接口定义了一组方法,但是这些被定义的方法不由接口直接实现,它不包含实现代码,而是由用户定义的类型来实现它的方法代码。
- 它是隐式实现的,对于一个具体的类型,不需要声明它实现了哪些接口,只需要提供接口所必需的方法。
- 如果用户定义的类型实现了接口类型声明的一组方法,那么用户类型的值就可以赋值给这个接口类型的值。对接口值的方法的调用,会执行接口值存储的用户定义类型的值对应的方法。
- 多个类型可以实现同一个接口,基于此可以模拟实现多态特性。
看 Go by Example 中提供的关于 interface 的例子:
package main
import (
"fmt"
"math"
)
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
}
参考: