Dawn's Blogs

分享技术 记录成长

0%

GO学习笔记 (1) 继承、接口、多态

继承

继承实现方法

在Go中,继承采用匿名结构体实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Student struct{
Name string
Age int
}

type Graduate struct{
Student // 继承Student
id int
}

type PostGraduates struct{
Student // 继承Student
Tutor string
}

继承的细节

  • 结构体和嵌入的匿名结构体中,有相同的字段和方法时,编译器采用就近原则。要访问匿名结构体的字段时,加上匿名结构体来区分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type A struct{
Name string
}

type B struct{
A // 继承A
Name string
}
func main() {
var b B
b.A.Name = "marry"

fmt.Println(b.Name) // 空串
fmt.Println(b.A.Name) // marry

b.Name = "smith"
fmt.Println(b.Name) // smith
}
  • 嵌入匿名结构体可以使用指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Person struct{
Name string
Age int
}

type Info struct{
Id int
Score int
}

type Student struct{
*Person
*Info // 多重继承,但并不推荐(多重继承使继承关系混乱)
}

func main() {
stu := Student{
&Person{"john", 18},
&Info{001, 90},
}
}
  • 嵌入多个匿名结构体,可以实现多重继承。因为多重继承使得继承关系混乱,所以并不推荐多重继承

接口

若多个类型都有一个或者多个共同点,那么就可以将这些共同的抽象出来聚合在一起,形成接口。接口与现实中的接口类似,如USB接口,USB接口可以插入手机、鼠标、键盘等设备,只要符合USB标准即可。

接口实现了高内聚,低耦合的思想

接口实现方法

Go中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。一般定义方法如下:

1
2
3
4
5
type InterfaceName interface{
// method1(参数利列表) 返回值列表
// method2(参数利列表) 返回值列表
// ...
}

下面抽象的定义一个USB接口

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
type Usb interface{  // 定义一个USB接口
Work()
Stop()
}

type Phone struct{ // 手机
// ...
}

func (p Phone) Work() {
// ...
}

func (p Phone) Stop() {
// ...
}

type Mouse struct{ // 鼠标
// ...
}

func (m Mouse) Work() {
// ...
}

func (m Mouse) Stop() {
// ...
}

type Computer struct {
// ...
}

func (c Computer) Working(usb Usb) {
usb.Start() // USB接口开始工作

if phone, ok := usb.(Phone); ok { // 类型断言
// 若是手机,执行一些特殊操作
// ...
}

usb.Stop() // USB接口停止工作
}

func main() {
computer := Computer{}
phone := Phone{}
mouse := Mouse{}

computer.Working(phone) // 接口“插入手机”
computer.Working(mouse) // 接口“插入鼠标”
}

接口的细节

  • 接口的实现是指,一个类型实现了被定义接口内的所有方法(隐式实现)。一个自定类型可以实现多个接口
  • 接口不能创建变量,但可以指向一个实现了该接口的自定义类型(不仅仅是结构体)的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type A interface{
test()
}

type Student struct{
Name string
}

// Student 实现了A接口
func (stu Student) test() {
fmt.Println("Stu test()")
}

func main() {
var stu Student // Stu 实现了A接口
var a A = stu
a.test() // "Stu test()"
}
  • 接口之间可以有继承关系,要实现所有被继承的接口
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
type B interface{
test1()
}

type C interface{
test2()
}

type A interface{
B // 继承B接口
C // 继承C接口
test3()
}


type Student struct {
Name string
}

// 用Student实现A接口:要实现所有被继承的接口

func (stu Student) test1() {
//...
}

func (stu Student) test2() {
//...
}

func (stu Student) test3() {
//...
}
  • interface是引用类型(指针)
  • 空接口interface{}没有实现任何方法,所以所有类型都实现了空接口,可以把任何变量都赋值给空接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type T interface{
// 空接口
}

func main() {
num1 := 6.17
num2 := 6

var t1 T = num1
fmt.Println(t1) // 6.17

var t2 interface{} = num2 // interface{}同样表示空接口
fmt.Println(t2) // 6
}

实践:实现Interface接口,对结构体切片进行排序

一个满足sort.Interface接口的(集合)类型可以被本包的函数进行排序。

1
2
3
4
5
6
7
8
type Interface interface {
// Len方法返回集合中的元素个数
Len() int
// Less方法报告索引i的元素是否比索引j的元素小
Less(i, j int) bool
// Swap方法交换索引i和j的两个元素
Swap(i, j int)
}

以Hero结构体实现Interface接口,对Hero的年龄进行排序:

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
65
66
67
68
69
70
package main

import (
"fmt"
"math/rand"
"sort"
)

type Hero struct {
Name string
Age int
}

// HeroSlice 是Hero结构体的切片类型
type HeroSlice []Hero

func (hs HeroSlice) Len() int {
return len(hs)
}

// Less 方法就是决定用什么标准排序
// 对Hero的年龄从小到大排序
func (hs HeroSlice) Less(i, j int) bool {
return hs[i].Age < hs[j].Age
}

func (hs HeroSlice) Swap(i, j int) {
hs[i], hs[j] = hs[j], hs[i]
}

func main() {
var heroes HeroSlice

for i := 0; i < 5; i++ {
hero := Hero{
Name: fmt.Sprintf("英雄-%d", rand.Intn(100)),
Age: rand.Intn(150),
}
heroes = append(heroes, hero)
}

// 排序前的顺序
fmt.Println("========排序前========")
for _, v := range heroes {
fmt.Println(v)
}

// 利用sort.Sort排序
sort.Sort(heroes)

fmt.Println("========排序后========")
for _, v := range heroes {
fmt.Println(v)
}
}

/*
========排序前========
{英雄-81 87}
{英雄-47 59}
{英雄-81 18}
{英雄-25 140}
{英雄-56 0}
========排序后========
{英雄-56 0}
{英雄-81 18}
{英雄-47 59}
{英雄-81 87}
{英雄-25 140}
*/

接口和继承的关系

  • 接口的实现可以看作对继承的补充
  • 接口和继承的解决问题不同:
    • 继承:解决代码复用性可维护性
    • 接口设计,设计和各种规范(方法),让其他自定义类型去实现这些方法
  • 接口比继承更灵活
    • 继承是is-a的关系
    • 接口只需满足like-a的关系
  • 接口在一定程度上实现了代码的解耦

多态

在GO中,多态是通过接口实现的。可以安装统一的接口调用不同的实现,这是接口变量就程序不同的形态。

接口体现多态

2种形式如下:

  • 多态参数:以接口作为函数的参数
  • 多态数组:接口数组可以存放任何实现了该接口的变量
    • 空接口数组任何类型都可以存放

类型断言

使用方法

由于接口是一般类型,不知道具体类型,若要从接口转换成具体类型,就需要类型断言

1
2
3
4
5
6
7
8
9
10
11
12
13
var z float32 = 1.1
var x interface{} // 空接口
x = z // 空接口可以接收任意类型
y, ok := x.(float32) // 类型断言,y=1.1

if ok {
// 断言成功
} else {
// 断言失败
}

// 不论断言成功与否,代码继续执行
// ....