Go不是一种典型的OO语言,它在语法上不支持类和继承的概念。
没有继承是否就无法拥有多态行为了呢?答案是否定的,Go语言引入了一种新类型—Interface,它在效果上实现了类似于C++的“多态”概念,虽然与C++的多态在语法上并非完全对等,但至少在最终实现的效果上,它有多态的影子。
那么,Go的Interface类型到底是什么呢?怎么使用呢?这正是本篇笔记试图说明的问题。
在说明Interface类型前,不得不先用Go的method(s)概念来热身,因为Go语言的interface与method(s)这两个语法有非常紧密的联系。
虽然Go语言没有类的概念,但它支持的数据类型可以定义对应的method(s)。本质上说,所谓的method(s)其实就是函数,只不过与普通函数相比,这类函数是作用在某个数据类型上的,所以在函数签名中,会有个receiver来表明当前定义的函数会作用在该receiver上。
关于methods的精确语法规范,可以参考language specification或Effective Go中的说明,这里略过。
注意:Go语言支持的除Interface类型外的任何其它数据类型都可以定义其method(而并非只有struct才支持method),只不过实际项目中,method(s)多定义在struct上而已。
在struct类型上定义method(s)的语法特性与C++中的struct支持的语法非常类似(c++中的struct定义了数据,此外也支持定义数据的操作方法),从这一点来看,我们可以把Go中的struct看作是不支持继承行为的轻量级的“类”。
2. What is Interface type in Go ?
GoLang官网language specification文档对interface type的概念说明如下:
An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface.
The value of an uninitialized variable of interface type is nil.
说实话,这段说明对新手来说比较晦涩,这正是本篇笔记试图解释清楚的地方。
从语法上看,Interface定义了一个或一组method(s),这些method(s)只有函数签名,没有具体的实现代码(有没有联想起C++中的虚函数?)。若某个数据类型实现了Interface中定义的那些被称为"methods"的函数,则称这些数据类型实现(implement)了interface。举个例子来说明。
- package main
- import (
- "fmt"
- "math"
- )
- type Abser interface {
- Abs() float64
- }
- type MyFloat float64
- func (f MyFloat) Abs() float64 {
- if f < 0 {
- return float64(-f)
- }
- return float64(f)
- }
- func main() {
- var a Abser
- f := MyFloat(-math.Sqrt2)
- a = f // a MyFloat implements Abser
- fmt.Println(a.Abs())
- }
根据上面的解释,Abs()是interface类型Abser定义的方法,而MyFloat实现了该方法,所以,MyFloat实现了Abser接口。
Interface类型的更通用定义可归纳如下:
- type Namer interface {
- Method1(param_list) return_type
- Method2(param_list) return_type
- ...
- }
- var ai Namer
它的method table ptr是不是与C++中类的虚函数表非常类似?而这正是interface类型的变量具有多态特性的关键:
ai共占2个机器字,1个为receiver字段,1个为method table ptr字段。ai可以被赋值为任何变量,只要这个变量实现了interface定义的method(s) set,赋值后,ai的receiver字段用来hold那个变量或变量副本的地址(若变量类型小于等于1个机器字大小,则receiver直接存储那个变量;若变量类型大于1个机器字,则Go底层会在堆上申请空间存储那个变量的副本,然后receiver存储那个副本的地址,即此时receiver是个指向变量副本的指针)。而由变量实现的接口method(s)组成的interface table的指针会填充到ai的method table ptr字段。当ai被赋值为另一个变量后,其receiver和method table ptr会更新为新变量的相关值。
关于interface类型内部实现细节,可以参考GoLang官网Blog推荐过的一篇文章“Go Data Structures: Interfaces”,写的很清楚,强烈推荐。
所以,如果某个函数的入参是个interface类型时,任何实现了该interface的变量均可以作为合法参数传入且函数的具体行为会自动作用在传入的这个实现了interface的变量上,这不正是类似于C++中多态的行为吗?
这正是Interface类型在Go语言中的威力。
引用<The Way to Go>一书第11.5节对interface类型的总结如下,值得每个Go学习者理解:
An interface is a kind of contract which the implementing type(s) must fulfill. Interfaces describe the behaviorof types, what they can do. They completely separate the definition of what an object can do from how it does it, allowing distinct implementations to be represented at different times by the same interface variable, which is what polymorphism essentially is.
Writing functions so that they accept an interface variable as a parameter makes them more general.
3. Interface“多态”特性实例
Go语言自带的标准Packages提供的接口很多都借助了Interface以具备“可以处理任何未知数据类型”的能力。例如被广泛使用的fmt包,其功能描述如下:
Package fmt implements formatted I/O with functions analogous to C's printf and scanf. The format 'verbs' are derived from C's but are simpler.
它除了可以格式化打印Go的built-in类型外,还可以正确打印各种自定义类型,只要这些自定义数据类型实现了fmt的Print API入参所需的interface接口。
以fmt包的Printf()函数为例,其函数签名格式如下:
- func Printf(format string, a ...interface{}) (n int, err error)
- type Stringer interface {
- String() string
- }
所以,自定义类型想要调用fmt.Printf()做格式化打印,那只需实现Stringer接口就行。
例如,下面是一段简单的打印代码:
- package main
- import "fmt"
- type IPAddr [4]byte
- func main() {
- addrs := map[string]IPAddr{
- "loopback": {127, 0, 0, 1},
- "googleDNS": {8, 8, 8, 8},
- }
- for n, a := range addrs {
- fmt.Printf("%v: %v\n", n, a)
- }
- }
- loopback: [127 0 0 1]
- googleDNS: [8 8 8 8]
- // exercise-stringer.go
- package main
- import "fmt"
- type IPAddr [4]byte
- // TODO: Add a "String() string" method to IPAddr.
- func (ip IPAddr) String() string {
- return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
- }
- func main() {
- addrs := map[string]IPAddr{
- "loopback": {127, 0, 0, 1},
- "googleDNS": {8, 8, 8, 8},
- }
- for n, a := range addrs {
- fmt.Printf("%v: %v\n", n, a)
- }
- }
执行结果符合需求:
- googleDNS: 8.8.8.8
- loopback: 127.0.0.1
【参考资料】
1. Golang Language Specification - Methods Expression
2. Golang Language Specification - Interface Type
3. <The Way to Go - A Thorough Introduction to the Go Programming Language>一书第11.1节
4. Go Data Structures: Interfaces
5. Go Package fmt
6. A Tour of Go - Exercise: Stringers
===================== EOF ====================
有疑问加站长微信联系(非本文作者)