第 13 章
在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型实现了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
13.1 接口的定义
使用 type
关键字可以定义接口:
type interface_name interface {
method()
}
13.2 接口的实现
创建类型或者结构体,并为其绑定接口定义的方法,接收者为该类型或结构体,方法名为接口中定义的方法名,这样就说该类型或者结构体实现了该接口。例如:
代码语言:go复制package main
import "fmt"
type Study interface {
learn()
}
type Student struct {
name string
}
func (s Student) learn() {
fmt.Printf("%s 在读 %s", s.name, s.book)
}
func main() {
student1 := Student{
name: "张三",
book: "《Go语言极简一本通》",
}
student1.learn()
}
上面的程序定义了一个名为 Study
的接口,接口中有未实现的方法 learn()
,这里还定义了名为 Student
的结构体,其绑定了方法 learn()
,也就隐式实现了 Study
接口,实现的内容是打印语句。
上面的例子使用了值接受者实现接口,下面的例子使用了指针接受者实现接口。
代码语言:go复制package main
import "fmt"
type Study interface {
learn()
}
...
type Worker struct {
name string
book string
by string
}
func (w *Worker) learn() {
fmt.Printf("%s 在读 %s,通过方式 %s", w.name, w.book, w.by)
}
func main() {
var s1 Study
var s2 Study
student2 := Student{
name: "李四",
book: "《Go语言极简一本通》",
}
s1 = student2
s1.learn()
student3 := Student{
name: "王五",
book: "Go语言微服务架构核心22讲",
}
s1 = &student3
s1.learn()
worker1 := Worker{
name: "老王",
book: "从0到Go语言微服务架构师",
by: "视频",
}
// s2 = worker1 // error
s2 = &worker1
s2.learn()
}
该程序定义了结构体 Student
,使用其作为值接受者实现 Study
接口。student2
的类型为 Student
, student2
赋值给 s1
,由于 Student
实现了接口变量 s1
所以会有输出。而接下来 s1
又被赋值为 &student3
,同样有输出。接下来的结构体 Worker
使用指针接受者实现 Study
接口。worker1
的类型为 Worker
, s2
被赋值为 &worker1
,所以会有输出。但如果把 s2
赋值为 worker1
会报错,对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此对于编译器无法自动获取 worker1
的地址,于是程序报错。
13.3 接口实现多态
使用接口可以实现多态,例如下面的程序,定义了名为 Study
的接口,接口中有方法 learn()
。程序中还定义了结构体 Student
和 Worker
,分别实现了 Study
接口,Student 的 learn name: "李四", book: "《Go语言极简一本通》"
而 Worker 的 learn 为 name: "张三",book: "从0到Go语言微服务架构师",by: "视频"
,利用的接口实现了不同的功能,这就是多态。
package main
func main() {
...
s2.learn()
worker1.learn()
}
13.4 接口的内部表示
可以把接口的内部看做 (type, value)
。type
是接口底层的具体类型(Concrete Type),而 value
是具体类型的值。
package main
import "fmt"
...
func ShowInterface(s Study) {
fmt.Printf("接口类型: %Tn,接口值: %vn", s, s)
}
func main() {
var s Study
s = student2
ShowInterface(s)
s.learn()
}
在上面的程序中,定义了 Study
接口,其中有 learn()
方法,结构体 Student
实现了该接口。使用 s = student2
语句我们把 student2
( Student
类型)赋值给了 s
( Study
类型),现在打印出 Study
的具体类型为 Student
,而 student2
的值为 name: "李四", book: "《Go语言极简一本通》"
。
13.5 空接口
空接口 是特殊形式的接口类型,没有定义任何方法的接口就称为空接口,可以说所有类型都至少实现了空接口,空接口表示为 interface{}
。例如,我们之前的写过的空接口参数函数,可以接受任何类型的参数:
package main
import "fmt"
func ShowType(i interface{}) {
fmt.Printf("类型: %T, 值: %vn", i, i)
}
func main() {
str := "从0到Go语言微服务架构师"
ShowType(str)
num := 3.14
ShowType(num)
}
上面的程序中我们定义了函数 ShowType
使用空接口作为参数,所以可以给这个函数传递任何类型的参数。
通过上面的例子不难发现接口都有两个属性,一个是值,而另一个是类型。对于空接口来说,这两个属性都为 nil
:
package main
import "fmt"
func main() {
var i interface{}
fmt.Printf("Type: %T, Value: %v", i, i)
// Type: <nil>, Value: <nil>
}
除了上面讲到的使用空接口作为函数参数的用法,空接口还有以下两种用法。
直接使用 interface{}
作为类型声明一个实例,这个实例就能承载任何类型的值:
package main
import "fmt"
func main() {
var i interface{}
i = "从0到Go语言微服务架构师"
fmt.Println(i) // Let's go
i = 3.14
fmt.Println(i) // 3.14
}
我们也可以定义一个接收任何类型的 array
、 slice
、 map
、 strcut
。例如:
package main
import "fmt"
func main() {
x := make([]interface{}, 3)
x[0] = "从0到Go语言微服务架构师"
x[1] = 3.14
x[2] = []int{1, 2, 3}
for _, value := range x {
fmt.Println(value)
}
}
空接口可以承载任何值,但是空接口类型的对象是不能赋值给另一个固定类型对象的。
代码语言:go复制package main
func main() {
var num = 1
var i interface{} = num
var str string = i // error
}
当空接口承载数组和切片后,该对象无法再进行切片。
代码语言:go复制package main
import "fmt"
func main() {
var s = []int{1, 2, 3}
var i interface{} = s
var s2 = i[1:2] // error
fmt.Println(s2)
}
13.6 类型断言
类型断言用于提取接口的底层值(Underlying Value)。使用 interface.(Type)
可以获取接口的底层值,其中接口 interface
的具体类型是 Type
。
package main
import "fmt"
func assert(i interface{}) {
value, ok := i.(int)
fmt.Println(value, ok)
}
func main() {
var x interface{} = 3
assert(x)
var y interface{} = "从0到Go语言微服务架构师"
assert(y)
}
13.7 类型选择
类型选择用于将接口的具体类型与 case
语句所指定的类型进行比较。它其实就是一个 switch
语句,但在 switch
后面跟的是 i.(type)
,并且每个 case
后面跟的是类型。
package main
import "fmt"
func getTypeValue(i interface{}) {
switch i.(type) {
case int:
fmt.Printf("Type: int, Value: %dn", i.(int))
case string:
fmt.Printf("Type: string, Value: %sn", i.(string))
default:
fmt.Printf("Unknown typen")
}
}
func main() {
getTypeValue(300)
getTypeValue("从0到Go语言微服务架构师")
getTypeValue(true)
}
13.8 实现多个接口
类型或者结构体可以实现多个接口,例如:
代码语言:go复制package main
import "fmt"
...
type Happy interface {
rest()
}
func (s Student) rest() {
fmt.Printf("%s 放学了,出去玩...", s.name)
}
func (w *Worker) rest() {
fmt.Printf("%s 下班了,吃大餐去...", w.name)
}
func main() {
worker2 := Worker{
name: "小明",
book: "从0到Go语言微服务架构师",
by: "视频",
}
worker2.learn()
worker2.rest()
}
13.9 接口的嵌套
虽然在 Go 中没有继承机制,但可以通过接口的嵌套实现类似功能。例如:
代码语言:go复制package main
import "fmt"
...
type Life interface {
Study
Happy
}
func main() {
worker2 := Worker{
name: "小明",
book: "从0到Go语言微服务架构师",
by: "视频",
}
worker2.learn()
worker2.rest()
}