广告位联系
返回顶部
分享到

Golang接口使用的教程

Golang 来源:互联网 作者:佚名 发布时间:2022-10-01 20:53:30 人浏览
摘要

go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、implements关键词。 一、概述 在 Go 语言中接口包

go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、implements关键词。

一、概述

在 Go 语言中接口包含两种含义:它既是方法的集合, 同时还是一种类型。在Go 语言中是隐式实现的,意思就是对于一个具体的类型,不需要声明它实现了哪些接口,只需要提供接口所必需的方法。

go语言通过隐性的方式实现了接口功能,相对比较灵活。

Go语言接口的特点:

  • interface 是方法或行为声明的集合
  • interface接口方式实现比较隐性,任何类型的对象实现interface所包含的全部方法,则表明该类型实现了该接口。
  • interface还可以作为一种通用的类型,其他类型变量可以给interface声明的变量赋值。
  • interface 可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。

二、接口类型

2.1 接口的定义

每个接口类型由任意个方法签名组成,接口的定义格式如下:

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

说明

  • 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略。

举个例子,定义一个包含Write方法的Writer接口。

1

2

3

type writer interface{

    Write([]byte) error

}

2.2 实现接口的条件

接口就是规定了一个需要实现的方法列表,在 Go 语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口。

示例

定义的Eater接口类型,它包含一个Eat方法。

1

2

3

4

// Eater 接口

type Eater interface {

    Eat()

}

有一个Dog结构体类型如下。

1

type Dog struct {}

因为Eater接口只包含一个Eat方法,所以只需要给Dog结构体添加一个Eat方法就可以满足Eater接口的要求。

1

2

3

4

//Dog类型的Eat方法

func (d Dog) Eat() {

    fmt.Println("吃骨头!")

}

这样就称为Dog实现了Eater接口。

完整代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// Eater 接口

type Eater interface {

    Eat()

}

 

type Dog struct {}

 

//Dog类型的Eat方法

func (d Dog) Eat() {

    fmt.Println("吃骨头!")

}

 

func main() {

    dog := Dog{}

    dog.Eat()

}

2.3 为什么需要接口

多数情况下,数据可能包含不同的类型,却会有一个或者多个共同点,这些共同点就是抽象的基础。

示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// Eater 接口

type Eater interface {

    Eat()

}

 

type Dog struct {}

 

//Dog类型的Eat方法

func (d Dog) Eat() {

    fmt.Println("狗狗喜欢吃骨头!")

}

 

type Cat struct {}

 

func (c Cat) Eat(){

    fmt.Println("小猫喜欢吃鱼!")

}

 

func main() {

    dog := Dog{}

    dog.Eat()

    cat := Cat{}

    cat.Eat()

}

从动物身上,可以抽象出来一个eat方法,这样即使在扩展其它动物进来,也只需要实现Eater 接口中的Eat()方法就可以完成对吃这个动作的调用。

接口可以理解为某一个方面的抽象,可以是多对一的(多个类型实现一个接口),这也是多态的体现。

2.4 接口类型变量

一个接口类型的变量能够存储所有实现了该接口的类型变量。

例如在上面的示例中,Dog和Cat类型均实现了Eater接口,此时一个Eater类型的变量就能够接收Cat和Dog类型的变量。

1

2

3

4

5

6

7

var x Eater // 声明一个Eater类型的变量x

a := Cat{}  // 声明一个Cat类型变量a

b := Dog{}  // 声明一个Dog类型变量b

x = a       // 可以把Cat类型变量直接赋值给x

x.Eat()     // 小猫喜欢吃鱼!

x = b       // 可以把Dog类型变量直接赋值给x

x.Eat()     // 狗狗喜欢吃骨头!

三、值接收者和指针接收者

通过下方一个示例来演示实现接口使用值接收者和使用指针接收者有什么区别。

定义一个Mover接口,它包含一个Move方法。

1

2

3

4

// Mover 定义一个接口类型

type Mover interface {

    Move()

}

3.1 值接收者实现接口

我们定义一个Dog结构体类型,并使用值接收者为其定义一个Move方法。

1

2

3

4

5

6

7

// Dog 狗结构体类型

type Dog struct{}

 

// Move 使用值接收者定义Move方法实现Mover接口

func (d Dog) Move() {

    fmt.Println("狗会动")

}

此时实现Mover接口的是Dog类型。

1

2

3

4

5

6

7

8

9

var x Mover    // 声明一个Mover类型的变量x

 

var d1 = Dog{} // d1是Dog类型

x = d1         // 可以将d1赋值给变量x

x.Move()

 

var d2 = &Dog{} // d2是Dog指针类型

x = d2          // 也可以将d2赋值给变量x

x.Move()

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是结构体类型还是对应的结构体指针类型的变量都可以赋值给该接口变量。

3.2 指针接收者实现接口

我们再来测试一下使用指针接收者实现接口有什么区别。

1

2

3

4

5

6

7

// Cat 猫结构体类型

type Cat struct{}

 

// Move 使用指针接收者定义Move方法实现Mover接口

func (c *Cat) Move() {

    fmt.Println("猫会动")

}

此时实现Mover接口的是*Cat类型,我们可以将*Cat类型的变量直接赋值给Mover接口类型的变量x。

1

2

3

var c1 = &Cat{} // c1是*Cat类型

x = c1          // 可以将c1当成Mover类型

x.Move()

但是不能给将Cat类型的变量赋值给Mover接口类型的变量x。

1

2

3

// 下面的代码无法通过编译

var c2 = Cat{} // c2是Cat类型

x = c2         // 不能将c2当成Mover类型

由于Go语言中有对指针求值的语法糖,对于值接收者实现的接口,无论使用值类型还是指针类型都没有问题。但是我们并不总是能对一个值求址,所以对于指针接收者实现的接口要额外注意。

四、类型与接口的关系

4.1 一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

示例

动物不仅有吃的属性,还有动的属性,可以通过定义两个接口,让同一个动物分别实现这两种属性

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

// Eater 接口

type Eater interface {

    Eat()

}

 

// Mover 接口

type Mover interface {

    Move()

}

 

type Dog struct {}

 

//Dog类型的Eat方法

func (d Dog) Eat() {

    fmt.Println("狗狗喜欢吃骨头!")

}

 

//Dog类型的Move方法

func (d Dog) Move(){

    fmt.Println("狗狗喜欢玩耍!")

}

 

func main() {

    //初始化结构体

    dog := Dog{}

 

    //dog实现了Eater和Mover两个接口

    eat := dog

    move := dog

 

    eat.Eat()   //对Eater类型调用Eat方法

    move.Move() //对Mover类型调用Move方法

}

程序中的结构体Dog分别实现了Eater和Mover两个接口中的方法。

4.2 多种类型实现同一接口

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

28

29

// WashingMachine 洗衣机

type WashingMachine interface {

    wash()

    dry()

}

 

// 甩干器

type dryer struct{}

 

// 实现WashingMachine接口的dry()方法

func (d dryer) dry() {

    fmt.Println("甩一甩")

}

 

// 洗衣机

type haier struct {

    dryer //嵌入甩干器

}

 

// 实现WashingMachine接口的wash()方法

func (h haier) wash() {

    fmt.Println("洗刷刷")

}

 

func main() {

    h := haier{}

    h.dry()

    h.wash()

}

五、接口嵌套

接口与接口之间可以通过互相嵌套形成新的接口类型。例如Go标准库io源码中就有很多接口之间互相组合的示例。

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

// src/io/io.go

 

type Reader interface {

    Read(p []byte) (n int, err error)

}

 

type Writer interface {

    Write(p []byte) (n int, err error)

}

 

type Closer interface {

    Close() error

}

 

// ReadWriter 是组合Reader接口和Writer接口形成的新接口类型

type ReadWriter interface {

    Reader

    Writer

}

 

// ReadCloser 是组合Reader接口和Closer接口形成的新接口类型

type ReadCloser interface {

    Reader

    Closer

}

 

// WriteCloser 是组合Writer接口和Closer接口形成的新接口类型

type WriteCloser interface {

    Writer

    Closer

}

对于这种由多个接口类型组合形成的新接口类型,同样只需要实现新接口类型中规定的所有方法就算实现了该接口类型。

接口也可以作为结构体的一个字段,我们来看一段Go标准库sort源码中的示例。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

// src/sort/sort.go

 

// Interface 定义通过索引对元素排序的接口类型

type Interface interface {

    Len() int

    Less(i, j int) bool

    Swap(i, j int)

}

 

 

// reverse 结构体中嵌入了Interface接口

type reverse struct {

    Interface

}

通过在结构体中嵌入一个接口类型,从而让该结构体类型实现了该接口类型,并且还可以改写该接口的方法。

1

2

3

4

// Less 为reverse类型添加Less方法,重写原Interface接口类型的Less方法

func (r reverse) Less(i, j int) bool {

    return r.Interface.Less(j, i)

}

Interface类型原本的Less方法签名为Less(i, j int) bool,此处重写为r.Interface.Less(j, i),即通过将索引参数交换位置实现反转。

在这个示例中还有一个需要注意的地方是reverse结构体本身是不可导出的(结构体类型名称首字母小写),sort.go中通过定义一个可导出的Reverse函数来让使用者创建reverse结构体实例。

1

2

3

func Reverse(data Interface) Interface {

    return &reverse{data}

}

这样做的目的是保证得到的reverse结构体中的Interface属性一定不为nil,否者r.Interface.Less(j, i)就会出现空指针panic。

六、空接口

Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。

空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

func main() {

    //定义一个空接口x,x变量可以接收任意的数据类型

    var x interface{}

    str := "Hello Go"

    x = str

    fmt.Printf("type:%T,value:%v\n",x,x)

 

    num := 10

    x = num

    fmt.Printf("type:%T,value:%v\n",x,x)

 

    bool := true

    x = bool

    fmt.Printf("type:%T,value:%v\n",x,x)

}

运行结果

type:string,value:Hello Go
type:int,value:10
type:bool,value:true

1、空接口作为函数的参数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

// 空接口作为函数参数

func show(a interface{}) {

    fmt.Printf("type:%T value:%v\n", a, a)

}

 

func main() {

    show(1)

    show(true)

    show(3.14)

    var mapStr = make(map[string]string)

    mapStr["name"] = "Leefs"

    mapStr["age"] = "12"

    show(mapStr)

}

运行结果

type:int value:1
type:bool value:true
type:float64 value:3.14
type:map[string]string value:map[age:12 name:Leefs]

2、map的值实现空接口

1

2

3

4

5

6

7

8

func main() {

    // 空接口作为 map 值

    var studentInfo = make(map[string]interface{})

    studentInfo["name"] = "Jeyoo"

    studentInfo["age"] = 18

    studentInfo["married"] = false

    fmt.Println(studentInfo)

}

运行结果

map[age:18 married:false name:Jeyoo]

3、切片实现空接口

1

2

3

4

func main() {

    var slice = []interface{}{"Jeyoo", 20, true, 32.2}

    fmt.Println(slice)

}

运行结果

[Jeyoo 20 true 32.2]

七、类型断言

一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。

如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:

x.(T)

说明

  • x: 表示类型为 interface{}的变量
  • T: 表示断言 x 可能是的类型

该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量,第二个值是一个布尔值,若为 true 则表示断言成功,为 false 则表示断言失败。

示例

1

2

3

4

5

6

7

8

9

10

func main() {

    var x interface{}

    x = "Hello GO"

    v, ok := x.(string)

    if ok {

        fmt.Println(v)

    } else {

        fmt.Println("类型断言失败")

    }

}

上面的示例中如果要断言多次就需要写多个 if 判断,这个时候我们可以使用 switch 语句来 实现:

注意:类型.(type)只能结合 switch 语句使用

1

2

3

4

5

6

7

8

9

10

11

12

13

// justifyType 对传入的空接口类型变量x进行类型断言

func justifyType(x interface{}) {

    switch v := x.(type) {

    case string:

        fmt.Printf("x is a string,value is %v\n", v)

    case int:

        fmt.Printf("x is a int is %v\n", v)

    case bool:

        fmt.Printf("x is a bool is %v\n", v)

    default:

        fmt.Println("unsupport type!")

    }

}

由于接口类型变量能够动态存储不同类型值的特点,所以很多初学者会滥用接口类型(特别是空接口)来实现编码过程中的便捷。

只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。切记不要为了使用接口类型而增加不必要的抽象,导致不必要的运行时损耗。

总结

在 Go 语言中接口是一个非常重要的概念和特性,使用接口类型能够实现代码的抽象和解耦,也可以隐藏某个功能的内部实现,但是缺点就是在查看源码的时候,不太方便查找到具体实现接口的类型。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://juejin.cn/post/7149046381640941575
相关文章
  • 基于GORM实现CreateOrUpdate的方法
    CreateOrUpdate 是业务开发中很常见的场景,我们支持用户对某个业务实体进行创建/配置。希望实现的 repository 接口要达到以下两个要求: 如果
  • Golang中的内存逃逸的介绍
    什么是内存逃逸分析 内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上 简单说就是编译器在编译
  • Golang自旋锁的介绍
    自旋锁 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。 它是为实现保护共享资源而提出的
  • Go语言读写锁RWMutex的源码

    Go语言读写锁RWMutex的源码
    在前面两篇文章中初见 Go Mutex、Go Mutex 源码详解,我们学习了Go语言中的Mutex,它是一把互斥锁,每次只允许一个goroutine进入临界区,可以保
  • Go项目实现优雅关机与平滑重启功能
    什么是优雅关机? 优雅关机就是服务端关机命令发出后不是立即关机,而是等待当前还在处理的请求全部处理完毕后再退出程序,是一种对
  • Go语言操作Excel利器之excelize类库的介绍
    在开发中一些需求需要通过程序操作excel文档,例如导出excel、导入excel、向excel文档中插入图片、表格和图表等信息,使用Excelize就可以方便
  • 利用Go语言快速实现一个极简任务调度系统

    利用Go语言快速实现一个极简任务调度系统
    任务调度(Task Scheduling)是很多软件系统中的重要组成部分,字面上的意思是按照一定要求分配运行一些通常时间较长的脚本或程序。在爬
  • GoLang中的iface 和 eface 的区别介绍

    GoLang中的iface 和 eface 的区别介绍
    GoLang之iface 和 eface 的区别是什么? iface和eface都是 Go 中描述接口的底层结构体,区别在于iface描述的接口包含方法,而eface则是不包含任何方
  • Golang接口使用的教程
    go语言并没有面向对象的相关概念,go语言提到的接口和java、c++等语言提到的接口不同,它不会显示的说明实现了接口,没有继承、子类、
  • go colly 爬虫实现示例介绍
    贡献某CC,go源码爬虫一个,基于colly,效果是根据输入的浏览器cookie及excel必要行列号,从excel中读取公司名称,查询公司法人及电话号码。
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计