第 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()
}


