接口 interface
Go语言接口(interface),Golang接口(interface) (opens new window)
# 1. 接口的引入
# 介绍
接口在Go语言有着至关重要的地位。如果说goroutine
和channel
是支撑起Go语言的并发模型
的基石,让Go语言在如今集群化
与多核化
的时代成为一道极为亮丽的风景,那么接口
是Go语言 整个类型系统
的基石,让Go语言在基础编程哲学的探索上达到前所未有的高度。
Go语言在编程哲学上是变革派,而不是改良派。这不是因为Go语言有goroutine和channel, 而更重要的是因为Go语言的类型系统,更是因为Go语言的接口
。Go语言的编程哲学因为有接口
而趋近完美。
接口只有方法声明,没有实现,也没有数据字段。
接口可以匿名嵌入到其他接口。
对象赋值给接口时,会发生拷贝。
只有当接口存储的类型和对象都是nil时,接口等于nil。
空接口可以接收任意的数据类型。
一个类型可以实现多个接口。
接口变量名习惯以 er 结尾。
2
3
4
5
6
7
接口类型是对其它类型行为的抽象和概括,接口类型不会和特定的实现细节绑定。
Go接口独特在它是隐式实现的,这是指:一个结构体只要实现了接口要求的所有方法,我们就说这个结构体实现了该接口。
2
接口
在现实世界也是有真实场景的,如同笔记本上都有USB插口,且不用担心这个插槽是为手机
、U盘
、平板
哪一个准备的,因为笔记本的usb插槽和各种设备的厂家统一了USB的插槽规范。
# 接口声明
每个接口类型由数个方法组成。接口的形式代码如下:
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
2
3
4
5
对各个部分的说明:
接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略,例如:
type writer interface{
Write([]byte) error
}
2
3
# 开发中常见的接口及写法
Go语言提供的很多包中都有接口,例如 io 包中提供的 Writer 接口:
type Writer interface {
Write(p []byte) (n int, err error)
}
2
3
这个接口可以调用 Write() 方法写入一个字节数组([]byte),返回值告知写入字节数(n int)和可能发生的错误(err error)。
类似的,还有将一个对象以字符串形式展现的接口,只要实现了这个接口的类型,在调用 String() 方法时,都可以获得对象对应的字符串。在 fmt 包中定义如下:
type Stringer interface {
String() string
}
2
3
Stringer 接口在Go语言中的使用频率非常高,功能类似于Java (opens new window)或者C# (opens new window)语言里的 ToString 的操作。
Go语言的每个接口中的方法数量不会很多。Go语言希望通过一个接口精准描述它自己的功能,而通过多个接口的嵌入和组合的方式将简单的接口扩展为复杂的接口。本章后面的小节中会介绍如何使用组合来扩充接口。
package main
// Writer 方式一: 普通接口
type Writer interface {
a()
}
// 方式二: 接口中方法带有值和返回值
type writer2 interface {
Write([]byte) error
}
type writer3 interface {
Write(p []byte) (n int, err error)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 实现接口的条件
Go语言实现接口的条件 (opens new window)
接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。接口的实现需要遵循两条规则才能让接口可用。
接口被实现的条件一:接口的方法与实现接口的类型方法格式一致
实现接口的方法签名不一致导致的报错
函数名不一致导致的报错
只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。
接口被实现的条件二:接口中所有方法均被实现
当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。
package main
import (
"fmt"
)
// DataWriter 定义一个数据写入器
type DataWriter interface {
WriteData(data string) error
//a() // 有未实现的方法时报错
}
// 定义文件结构,用于实现DataWriter
type file struct {
}
// WriteData 实现DataWriter接口的WriteData方法
// func (d *file) WriteDataX(data string) error { 函数名称和接口不一致,报错
// func (d *file) WriteData(data int) error {函数实现接口的传值不一致,报错
func (d *file) WriteData(data string) error {
// 模拟写入数据
fmt.Println("WriteData:", data)
return nil
}
func main() {
// 实例化file
f := new(file)
// 声明一个DataWriter的接口
var writer DataWriter
// 将接口赋值f,也就是*file类型
writer = f
// 使用DataWriter接口进行数据写入
writer.WriteData("data")
}
/*
WriteData: data
*/
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
# 2. Go语言类型与接口的关系
Go语言类型与接口的关系 (opens new window)
在Go语言中类型和接口之间有一对多和多对一的关系,下面将列举出这些常见的概念,以方便读者理解接口与类型在复杂环境下的实现关系。
# 一个类型可以实现多个接口
package main
import "fmt"
type AInterface interface {
a()
}
type BInterface interface {
b()
}
type Stu struct {
}
func (s Stu) a() {
fmt.Println("aaaa")
}
func (s Stu) b() {
fmt.Println("bbbb")
}
func main() {
// 一个结构体实现多个接口
var s Stu
var a AInterface = s // 绑定接口
var b BInterface = s
a.a()
b.b()
}
/*
aaaa
bbbb
*/
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
# 一个接口可以实现多个类型
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。也就是说,使用者并不关心某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌入到一个结构体中拼凑起来共同实现的。
// 一个服务需要满足能够开启和写日志的功能
type Service interface {
Start() // 开启服务
Log(string) // 日志输出
}
// 日志器
type Logger struct {
}
// 实现Service的Log()方法
func (g *Logger) Log(l string) {
}
// 游戏服务
type GameService struct {
Logger // 嵌入日志器
}
// 实现Service的Start()方法
func (g *GameService) Start() {
}
var s Service = new(GameService)
s.Start()
s.Log(“hello”)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3. 接口的嵌套组合(一个接口可以保函多个接口)
在Go语言中,不仅结构体与结构体之间可以嵌套,接口与接口间也可以通过嵌套创造出新的接口。
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。
package main
import "fmt"
type CInterface interface {
c()
}
type BInterface1 interface {
b()
}
type AInterface1 interface {
BInterface1
CInterface
a()
}
type Stu1 struct {
}
func (s Stu1) a() {
fmt.Println("a")
}
func (s Stu1) b() {
fmt.Println("b")
}
func (s Stu1) c() {
fmt.Println("c")
}
func main() {
var s Stu1
var a AInterface1 = s // 绑定方法
// 继承多个接口,需要实现接口中的所有方法
a.a()
a.b()
a.c()
}
/*
a
b
c
*/
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
# 4. 接口和类型之间的转换
Go语言接口和类型之间的转换 (opens new window)
# 5. 空接口类型(interface{})
Go语言空接口类型(interface{}) (opens new window)
# 6. 接口注意事项
1.接口本身不能创建实例,但是可以指向一个实现了该接口的变量实例,如结构体
2.接口中所有方法都没有方法体,是没有实现的方法
3.Go中不仅是struct可以实现接口,自定义类型也可以实现接口,如type myInt int 自定义类型
4.一个自定义类型,只有实现了某个接口,才可以将自定义类型的实例变量,赋给接口类型,否则报错missing xx method
5.一个自定义类型,可以实现多个接口(实现多个接口的所有方法)
6.接口类型不得写入任何变量 如
type Usb interface{
method1()
method2()
Name string //错误,编译器不通过
}
7.接口A可以继承多个别的接口B、接口C,想要实现A,也必须实现B、C所有方法,称作接口组合
8.interface类型,默认是指针(引用类型),如果没初始直接使用,输出nil,可以赋给实现了接口的变量
9.空接口interface{},没有任何类型,也就是实现了任何类型,可以吧任何一个变量赋给空接口
10.匿名组合的接口,不可以有同名方法,否则报错duplicate method
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。
# 2. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
# 3. 一个自定义类型可以实现多个接口
package main
import "fmt"
type AInterface interface{
a()
}
type BInterface interface{
b()
}
type Stu struct{
}
func (s Stu) a(){
fmt.Println("aaaa")
}
func (s Stu) b(){
fmt.Println("bbbb")
}
func main(){
var s Stu
var a AInterface = s
var b BInterface = s
a.a()
b.b()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 4. 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现。
package main
import "fmt"
type CInterface interface{
c()
}
type BInterface interface{
b()
}
type AInterface interface{
BInterface
CInterface
a()
}
type Stu struct{
}
func (s Stu) a(){
fmt.Println("a")
}
func (s Stu) b(){
fmt.Println("b")
}
func (s Stu) c(){
fmt.Println("c")
}
func main(){
var s Stu
var a AInterface = s
a.a()
a.b()
a.c()
}
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