接口 interface

接口定义

接口(interface)是调用方和实现方均需要遵守的一种约束,大家按照统一的方法命名、参数类型和数量来协调逻辑处理的过程。实际上,接口就是一组不需实现的方法声明,不能包含任何变量。到某个自定义类型要使用的时候,在根据具体情况把这些方法写出来(实现)。
Go的接口是非侵入式设计的,接口编写者无需知道接口被哪些类型实现,接口实现者只需要知道实现的是什么样子的接口,但无需指明实现了哪个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。

接口语法:

1
2
3
4
5
type 接口类型名 interface {
方法名1(参数列表) 返回值列表
方法名2(参数列表) 返回值列表
...
}

注意: - Go语言的接口在命名时,一般会在单词后面添加er,如写操作的接口叫做Writer - 当方法名首字母大写,且实现的接口首字母也是大写,则该方法可以被接口所在包之外的代码访问 - 参数列表和返回值列表中的变量名可以被忽略,如:type writer interfae{ Write([]byte) error} - Go标准包,每个接口包含的方法很少,Go希望一个接口精准描述它自己的功能。

接口实现

接口实现的条件: - 方法与接口中的方法签名一致(方法名、参数列表、返回列表都必须一致) - 接口中所有的方法都必须被实现

示例:

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
//定义一个数据写入接口
type DataWriter interface {
WriteData(data interface{}) error
}

//定义文件对象,实现WriteData()方法
type file struct {

}

//实现接口
func (f *file) WriteData(data interface{}) error {
fmt.Print("WriteData: ", data) //模拟写入
return nil
}

func main() {

f := new(file)

var writer DataWriter //声明一个DataWriter接口

writer = f //将接口赋值给f,即*file类型

writer.WriteData("data...") //模拟数据写入

}

注意: - 如果在编译到writer = f发现实现接口的方法签名不一致,则会报错:does not implement
- 如上所示,Go无须像Java那样显式声明实现了哪个接口,即为非侵入式。 - 使用writer接口调用了接头体file的方法,也可以理解为实现了面向对象中的多态

接口与类型的关系

类型和接口之间有一对多和多对一的关系,即: - 一个类型可以实现多个接口,接口间是彼此独立的,互相不知道对方的实现 - 多个类型也可以实现相同的接口。

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
type Service interface {
Start()
Log(string)
}

// 日志器
type Logger struct {
}
//日志输出方法
func (g *Logger) Log(s string){
fmt.Println("日志:", s)
}

// 游戏服务
type GameService struct {
Logger
}
// 实现游戏服务的Start方法
func (g *GameService) Start() {
fmt.Println("游戏服务启动")
}

func main() {
s := new(GameService)
s.Start()
s.Log("hello")
}

在上述案例中,即使没有接口也能运行,但是当存在接口时,会隐式实现接口,让接口给类提供约束。

接口嵌套

Go中不仅结构体之间可以嵌套,接口之间也可以嵌套。接口与接口嵌套形成了新的接口,只要接口的所有方法被实现,则这个接口中所有嵌套接口的方法均可以被调用。

1
2
3
4
5
6
7
8
9
10
11
12
type Writer interface {
Write(p []byte) (n int, e error)
}

type Closer interface {
Close() error
}

type WriteCloser interface {
Writer
Closer
}

接口断言

类型断言定义

Go中使用接口断言(type assertions)将接口转换成另外一个接口,也可以将接口转换为别的类型,这是非常常见的使用。

类型断言格式:

1
2
t := i.(T)			//不安全写法:如果i没有完全实现T接口的方法,这个语句将会触发宕机
t, ok := i.(T) // 安全写法:如果接口未实现接口,将会把ok掷为false,t掷为T类型的0值
- i代表接口变量 - T代表转换的目标类型 - t代表转换后的变量

接口转换为其他接口

实现某个接口的类型同时实现了另外一个接口,此时可以在两个接口间转换。

鸟和猪都具有不同的特性,鸟可以飞,猪不能飞,但是二者都可以走,如果使用结构体实现鸟和猪,让他具备各自的特性Fly()Walk()方法就让鸟和猪各自实现了飞行动物接口Flyer和行走动物接口Walker。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//飞行动物接口
type Flyer interface {
Fly()
}

//行走动物接口
type Walker interface {
Walk()
}


//鸟类
type bird struct {

}

//鸟类实现飞行动物接口
func (b *bird) Fly() {
fmt.Println("bird: fly")
}

//鸟类实现行走动物接口
func (b *bird) Walk() {
fmt.Println("bird: walk")
}


//猪类
type pig struct {

}

//猪类实现行走动物接口,没有实现飞行接口
func (p *pig) Walk() {
fmt.Println("pig: walk")
}

func main() {

//创建动物名字到实例的映射
animals := map[string]interface{}{
"bird": new(bird),
"pig": new(pig),
}

for name, obj := range animals {

// 断言:判断对象是否是飞行动物,行走动物
f, isFlyer := obj.(Flyer)
w, isWalker := obj.(Walker)

fmt.Printf("name is %s \n", name)

if isFlyer {
f.Fly()
}

if isWalker {
w.Walk()
}

}

}

将接口转换为其他类型

在上述代码中,可以实现将接口转换为普通的指针类型,例如将Walker接口转换为*pig类型:

1
2
3
4
5
6
7
p1 := new(pig)

var a Walker = p1

p2 := a.(*pig)

fmt.Printf("p1=%p p2=%p", p1, p2) //发现二者相同

类型查询

在Go中,可以直接询问接口指向的对象实例的类型:

1
2
3
4
5
6
var v1 interfaceP{} = ...
switch v := v1.(type) {
case int:
case string:
...
}

接口实现多态

多态是面向对象的三大特性之一。

示例:动物都具备Move的动作,如果是鸟类的Move,则是飞翔,如果是鱼类的Move则是游泳

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
type Animal interface {
Move()
}

type Bird struct {
Name string
}

func (b *Bird) Move() {
fmt.Println("bird fly...")
}

type Fish struct {
Name string
}

func (f *Fish) Move() {
fmt.Println("fish swim...")
}

func Factory(name string) Animal {
switch name {
case "bird":
return &Bird{}
case "fish":
return &Fish{}
default:
return nil
}

}

func main() {
a := Factory("bird")
a.Move()
}

空接口

空接口定义

空接口是接口的特殊形式,没有任何方法,因此任何类型都无须实现空接口,故而空接口可以保存任何值,(可以简单的将空接口理解为Java中的Object)。

1
2
3
4
5
6
7
var any interface{}

any = 1
fmt.Println(any)

any = "hello"
fmt.Println(any)

从空接口获取值

保存到空接口的值,如果直接取出指定类型的值时,会发生编译错误:

1
2
3
var a int = 1
var i interface{} = a
var b int = i //这里编译报错(类型不一致),可以这样做:b := i

空接口值比较

类型不同的空接口比较:

1
2
3
4
var a interface{} = 100
var b interface{} = "hi"

fmt.Println(a == b) //false

不能比较空接口中的动态值:

1
2
3
var c interface{} = []int{10}
var d interface{} = []int{20}
fmt.Println(c == d) //运行报错

空接口的类型和可比较性: | 类型 | 说明 | | ---- | ---- | | map | 不可比较,会发生宕机错误 | | 切片 | 不可比较,会发生宕机错误 | | 通道 | 可比较,必须由同一个make生成,即同一个通道才是true | | 数组 | 可比较,编译期即可知道是否一致 | | 结构体 | 可比较,可诸葛比较结构体的值 | | 函数 | 可比较 |

作者

ฅ´ω`ฅ

发布于

2021-06-10

更新于

2021-06-10

许可协议


评论