第 13 章
在 Go 语言中, 接口 就是方法签名(Method Signature)的集合。在面向对象的领域里,接口定义一个对象的行为,接口只指定了对象应该做什么,至于如何实现这个行为,则由对象本身去确定。当一个类型实现了接口中的所有方法,我们称它实现了该接口。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
13.1 接口的定义
使用 type
关键字可以定义接口:
1 2 3
| type interface_name interface { method() }
|
13.2 接口的实现
创建类型或者结构体,并为其绑定接口定义的方法,接收者为该类型或结构体,方法名为接口中定义的方法名,这样就说该类型或者结构体实现了该接口。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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
接口,实现的内容是打印语句。
上面的例子使用了值接受者实现接口,下面的例子使用了指针接受者实现接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| 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 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: "视频"
,利用的接口实现了不同的功能,这就是多态。
1 2 3 4 5 6 7
| package main
func main() { ... s2.learn() worker1.learn() }
|
13.4 接口的内部表示
可以把接口的内部看做 (type, value)
。type
是接口底层的具体类型(Concrete Type),而 value
是具体类型的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package main
import "fmt"
...
func ShowInterface(s Study) { fmt.Printf("接口类型: %T\n,接口值: %v\n", 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{}
。例如,我们之前的写过的空接口参数函数,可以接受任何类型的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package main
import "fmt"
func ShowType(i interface{}) { fmt.Printf("类型: %T, 值: %v\n", i, i) }
func main() { str := "从0到Go语言微服务架构师" ShowType(str) num := 3.14 ShowType(num) }
|
上面的程序中我们定义了函数 ShowType
使用空接口作为参数,所以可以给这个函数传递任何类型的参数。
通过上面的例子不难发现接口都有两个属性,一个是值,而另一个是类型。对于空接口来说,这两个属性都为 nil
:
1 2 3 4 5 6 7 8 9
| package main
import "fmt"
func main() { var i interface{} fmt.Printf("Type: %T, Value: %v", i, i) }
|
除了上面讲到的使用空接口作为函数参数的用法,空接口还有以下两种用法。
直接使用 interface{}
作为类型声明一个实例,这个实例就能承载任何类型的值:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package main
import "fmt"
func main() { var i interface{}
i = "从0到Go语言微服务架构师" fmt.Println(i)
i = 3.14 fmt.Println(i) }
|
我们也可以定义一个接收任何类型的 array
、 slice
、 map
、 strcut
。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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) } }
|
空接口可以承载任何值,但是空接口类型的对象是不能赋值给另一个固定类型对象的。
1 2 3 4 5 6 7
| package main
func main() { var num = 1 var i interface{} = num var str string = i }
|
当空接口承载数组和切片后,该对象无法再进行切片。
1 2 3 4 5 6 7 8 9 10 11 12
| package main
import "fmt"
func main() { var s = []int{1, 2, 3}
var i interface{} = s
var s2 = i[1:2] fmt.Println(s2) }
|
13.6 类型断言
类型断言用于提取接口的底层值(Underlying Value)。使用 interface.(Type)
可以获取接口的底层值,其中接口 interface
的具体类型是 Type
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
后面跟的是类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main
import "fmt"
func getTypeValue(i interface{}) { switch i.(type) { case int: fmt.Printf("Type: int, Value: %d\n", i.(int)) case string: fmt.Printf("Type: string, Value: %s\n", i.(string)) default: fmt.Printf("Unknown type\n") } }
func main() { getTypeValue(300) getTypeValue("从0到Go语言微服务架构师") getTypeValue(true) }
|
13.8 实现多个接口
类型或者结构体可以实现多个接口,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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 中没有继承机制,但可以通过接口的嵌套实现类似功能。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package main
import "fmt"
...
type Life interface { Study Happy }
func main() { worker2 := Worker{ name: "小明", book: "从0到Go语言微服务架构师", by: "视频", } worker2.learn() worker2.rest() }
|
如何学习Go语言微服务,快速步入架构师
添加微信 |
公众号更多内容 |
 |
 |