Go语言基础 — 接口
Golang Basic — Interface
Duck Typing
说Go的接口就必须先了解duck typing的概念。很多其他的语言都支持这个概念。
首先上图的这个东西是鸭子吗?
鸭子的定义:
鸭子 英文名称:Duck。脊索动物门,脊椎动物亚门,鸟纲雁形目,鸭科鸭属动物,是由野生绿头鸭和斑嘴鸭驯化而来。是一种常见家禽。鸭是雁形目鸭科鸭亚科水禽的统称。是一种水、陆两栖动物。但不能在水中待太久,是卵生动物。中文学名鸭界动物界门脊索动物门亚 门脊椎动物亚门纲鸟纲亚 纲今鸟亚纲目雁形目
按照传统的定义,上面这个图肯定不是鸭子。但是按照duck typing的定义来说,上图这个东西是一只鸭子。因为他长得像鸭子。Duck Typing关注的是描述事物的外部行为而非内部结构。
但是严格来说Duck Typing的定义中要求动态绑定,但是Go是编译就绑定了,所以Go只能说是类似Duck Typing。
Python和C++中的Duck Typing
只有在运行的时候才知道传入的retriever有没有get,如果没有get方法,download的就会报错,但是如果download和retriever是两组不同的人开发。传入download的retriever必须有get方法这个信息通常会写在注释里。
同样C++也有类似的写法,然后C++可以做到编译的时候知道传入的retriever有没有get。同时这个信息还是需要写在注释里。
JAVA中的类似的东西
JAVA的这种做法确实很安全,传入的参数必须实现Retriever,也不用写注释了,也不会编译错误了。但是这已经不是Duck Typing,因为你必须实现Retriever接口。导致download不能实现多个接口。(比如read和write)
Go的Duck Typing
兼备Python的灵活性和JAVA的类型检查
接口的定义
Go语言中的接口由使用者定义
使用者
实现者
实际使用的时候
接口的值类型
接口内部
接口内部可以有实现者的类型和实现者
也可以用实现者的指针
检测类型
由于Go语言的所有类型都是值类型,所以在实际使用实现了接口的方法的时候r := mock.Retriever{"test"}
中的r除了保存了值,也保存了实现者的类型。
查看interace的类型有2中方法:
- Type assertion
注意这里由于实现者里的方法是传的指针,所以r.(*mock.Retriever)
也需要时指针。不然会报错。
- Switch
- 打印类型
空接口
interface{}
代表空接口,可以代表任何类型。
假设有一个队列
如果定义为int的话当然只能往里面加int,但是如果您定义成空接口,就可以传任何值。
但是在往里面你传值的时候,你可以通过方法限定值得类型
这样的话如果q.push("abc")
的话在编译的时候就能检测的错误。
还有一种方法是在append的时候做转换。
但是这样做的话就只能在执行的时候才能检查到错误了所以不推荐。
接口的组合
接口内部可以加入其他接口来进行组合。比如很多写入操作的参数都是reader或者writer
原生接口的例子
举个原生包的例子,比如写入文件。
使用者fmt.Fprintf
接受一个io.writer,io.writer是一个接口,里面定义了一个write方法。
再看作为实现者的os.file构造体里面也实现了一个write方法,按照duck typing的规则,file实现了write方法,所以file也是一个writer,所以file也可以传给fmt.Fprintf
。
Go语言接口的好处
这样好处是显而易见的,我们拥有非常高的灵活性,同时保证在编译前就能检测到错误。尤其是在团队开发的时候,在其他编程语言当中,都是谁提供服务,谁提供接口。你需要调用我的服务,就必须声明你实现了我的接口。
而这在逻辑上实际是说不通的,服务实现者怎么会确切的知道服务使用者的具体需求呢?当需求发生变化的时候,服务实现者就需要考虑使用者的需求,从而设计接口。而从理论上来说,每一个服务的开发人员都应该专注于自己的服务。
而go语言不同。go语言的接口是非侵入式接口,只要调用者本身实现了该接口的全部方法,就默认实现了该接口(事实上也确实是实现了这个接口),而不需要显示的声明实现某个接口。这极大的方便了接口的调用,开发人员不必再需要苦想接口的粒度,只需要专注功能函数的实现就可以了。