[TOC]
Go语言中的接口是什么?
| * 1)是一种类型 |
| * 2)是一组功能的集合,而不是一个功能 |
| * 3)接口内只定义函数,但不涉及函数实现 |
| * 4)这些功能是相关的,都是相关的功能,比如动物的接口里应该放动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */ |
为何要用接口?
| Go语言提倡面向接口编程 |
| |
| 接口规定了必须要有哪些函数以及函数的名字是什么,然后让“子类“去实现接口的函数,以此来把多个子类统一/规范起来 |
| |
| 这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。 |
| |
| |
| 1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。 |
| |
| 2. 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合 |
| |
| 2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。 |
| |
| 2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样 |
接口是一种类型,该类型内部包含多个未实现的函数/方法,定义语法如下
| type 接口类型名 interface{ |
| 方法名1( 参数列表1 ) 返回值列表1 |
| 方法名2( 参数列表2 ) 返回值列表2 |
| … |
| } |
注意:
-
1、接口类型的命名应该见名知意,一般会在单词后加er,
例如:
| - 有读操作的接口:Reader |
| - 写操作的接口:Writer |
| - 关闭操作的接口:Closer |
| - 其他:Printer、Logger、Converter等等 |
还有一些不常用的方式(当后缀 er
不合适时),比如Recoverable
,此时接口名以 able
结尾,或者以 I
开头(像 .NET
或 Java
中那样)。
-
2、Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。
-
3、接口名以及方法名的首字母均为大写,该方法才可以被其他包导入
例如:
| package io |
| |
| type Reader interface { |
| Read(p []byte) (n int, err error) |
| } |
| type Closer interface { |
| Close() error |
| } |
-
4、参数列表与返回值列表中的参数名字可以省略
例如
| type Reader interface { |
| Read([]byte) (int, error) |
| } |
Go中,子类不需要显式声明它实现了某个接口,只要“子类”(事实上,go语言中struct充当的就是类的概念)实现了接口中全部的函数那就是实现了接口,当然,Go的这种风格也可以被称为隐式实现,并且即使接口在类型之后才定义,二者处于不同的包中,被单独编译:只要类型实现了接口中的方法,它就实现了此接口。
所有这些特性使得接口具有很大的灵活性。
| |
| |
| |
| type animaler interface { |
| eat() |
| sleep() |
| } |
| |
| |
| type dog struct { |
| name string |
| class string |
| } |
| |
| |
| |
| func (d dog) eat() { |
| fmt.Printf("一只名为【%v】的【%v】正在吃东西\n",d.name,d.class ) |
| } |
| |
| |
| func (d dog) sleep() { |
| fmt.Printf("一只名为【%v】的【%v】正在睡觉\n",d.name,d.class ) |
| } |
| |
| |
| func (d dog) run() { |
| fmt.Printf("一只名为【%v】的【%v】正在奔跑\n",d.name,d.class ) |
| } |
| |
| func main() { |
| d:=dog{ |
| name: "天蓬元帅", |
| class: "哈士奇", |
| } |
| d.eat() |
| d.sleep() |
| d.run() |
| } |
Go语言中的接口是非侵入式的,即便是拿掉了接口,也不影响”子类“,所以说Go的接口也属于duck-type
不像大多数面向对象编程语言,在 Go 语言中可以声明接口类型的值,简称接口值,如下所示,a是一个多字(multiword)数据结构,它的值是 nil
。
| var a animaler |
| |
| |
| 指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。 |
一个接口变量就可以存储(或“指向”,接口变量类似于指针,但接口与指针不完全是一回事)任何类型的具体值,只要这个值实现了该接口类型的所有方法,所以,但凡是实现了接口的变量都可以赋值给a,
| a=d |
| |
| |
| 赋值成功后,此时接口类型变量a中所包含的方法列表中的指针会指向被实现的接口方法,即指向d的方法,于是我们可以通过“a.接口中规定的方法”来访问d中对应的具体实现功能,如下所示 |
我们可以不考虑具体的类型,而直接使用对象的方法,比如我们只需要记住d是一个动物就可以完全按照动物的方法去调用
也就说我们只需要学习接口中的方法就可以了,用就用接口类型的变量即可,具体的类型不需要考虑,这,就是接口类型变量的好处
关于接口变量的赋值,正常的变量赋值都适用
| var a animaler = d |
| 或 |
| a:=animaler(d) |
| 或 |
| a:=d |
如果我们仅仅只是想验证一下,某个结构体是否实现了接口,可以这么做
| |
| var _ 接口类型 = &结构体{} |
| |
| |
| var _ animaler = &dog{} |
在介绍结构体的方法时,我们提及,方法的接收者可以是值也可以是指针,当结构体在实现接口规定的方法时,
无论采用的是值接收者还指针接收者,都算是实现了接口,都可以将实例化好的结构体对象赋值给接口变量,但是
1、如果采用的是全部都是值接收者,那么既可以将实例化好的结构体对象赋值给接口变量,也可以将实例化好的结构体对象的指针赋值给接口变量
| |
| package main |
| |
| import "fmt" |
| |
| |
| type animaler interface { |
| eat() |
| sleep() |
| } |
| |
| |
| type dog struct { |
| name string |
| class string |
| } |
| |
| |
| |
| func (d dog) eat() { |
| fmt.Printf("一只名为【%v】的【%v】正在吃东西\n", d.name, d.class) |
| |
| } |
| |
| |
| func (d dog) sleep() { |
| fmt.Printf("一只名为【%v】的【%v】正在睡觉\n", d.name, d.class) |
| } |
| |
| func main() { |
| d := dog{ |
| name: "天蓬元帅", |
| class: "哈士奇", |
| } |
| |
| var x animaler |
| |
| x=d |
| x.eat() |
| x.sleep() |
| |
| x=&d |
| x.eat() |
| x.sleep() |
| } |
2、如果采用的包含指针接收者,那么只能将实例化好的结构体对象的指针赋值给接口变量
| |
| package main |
| |
| import "fmt" |
| |
| |
| type animaler interface { |
| eat() |
| sleep() |
| } |
| |
| |
| type dog struct { |
| name string |
| class string |
| } |
| |
| |
| |
| func (d *dog) eat() { |
| fmt.Printf("一只名为【%v】的【%v】正在吃东西\n", d.name, d.class) |
| |
| } |
| |
| |
| func (d *dog) sleep() { |
| fmt.Printf("一只名为【%v】的【%v】正在睡觉\n", d.name, d.class) |
| } |
| |
| func main() { |
| d := dog{ |
| name: "天蓬元帅", |
| class: "哈士奇", |
| } |
| |
| var x animaler |
| |
| |
| |
| |
| |
| x=&d |
| x.eat() |
| x.sleep() |
| } |
可以多个类型/结构体实现同一个接口,也可以一个类型实现多个接口
| package main |
| |
| import "fmt" |
| |
| |
| type animaler interface { |
| eat() |
| sleep() |
| } |
| |
| |
| type hunter interface { |
| hunting() |
| } |
| |
| |
| type dog struct { |
| name string |
| class string |
| } |
| |
| func (d dog) eat() { |
| fmt.Printf("一只名为【%v】的【%v】正在吃东西\n",d.name,d.class ) |
| } |
| |
| func (d dog) sleep() { |
| fmt.Printf("一只名为【%v】的【%v】正在睡觉\n",d.name,d.class ) |
| } |
| |
| func (d dog) hunting() { |
| fmt.Printf("一只名为【%v】的【%v】正在捕猎\n",d.name,d.class ) |
| |
| } |
| |
| |
| type cat struct { |
| name string |
| } |
| |
| func (c cat) sleep() { |
| fmt.Printf("一只名为【%v】的小花猫正在睡觉\n",c.name) |
| |
| } |
| |
| func main() { |
| d:=dog{ |
| name: "天蓬元帅", |
| class: "哈士奇", |
| } |
| d.eat() |
| d.sleep() |
| d.hunting() |
| |
| |
| var _ animaler = &dog{} |
| var _ hunter = &dog{} |
| |
| c:=cat{"egon"} |
| c.sleep() |
| } |