系列文章:
golang利用组合实现继承,和php或java面向对象的继承有何不同
本篇是对「Go语言底层原理剖析」的学习与记录
1. 入门
Go语言没有任何形式的基于类型的继承,取而代之的是使用接口来实现扁平化、面向组合的设计模式。
在Go语言中,interface是 其他类型可以实现的方法签名的集合。
有两种类型,实现原理有很大不同:一种是带方法签名的接口(一般称为接口),一种是空接口(一般称为空接口)
一般最佳实践(官方库&知名项目):
- 接口又嵌入接口,保持深度在0或1为最佳。
- 接口中直接定义的方法数量10个之内最佳。
(下面把自定义的接口成为一级接口,其包含的接口成为二级接口,以此类推)
嵌入的接口的数量: 标准库中接口绝大多数都不会再嵌套接口,少数会再嵌套一层接口,最多嵌套的数量为4,是mime/multipart.File中的File接口:
1 | type File interface { |
131个接口为0,27个为1,8个为2,3个为3,1个为4
嵌入的接口深度: 接口嵌入接口,嵌入的接口再嵌入接口..较深的接口降低了代码的可读性,提高了代码复杂度。
标准库中不使用太深的嵌入方式,少量接口有一层嵌入
131个接口没有嵌入,29个接口嵌入了一层 (和上面相对应)
接口中直接定义的方法的数量: (一级接口)直接定义的方法的数量,不包括嵌入接口引入的方法。
标准库中接口定义的直接方法的数量都很少,不会设置很多方法,接口都比较精巧干练。 比较特殊的是reflect/Type接口,定义了31个直接方法
接口中总的方法的数量:把(一级接口)直接定义的方法和嵌入接口引入的方法加和
标准库中接口的总方法数量基本都在8个以下,最多的当然还是reflect/Type接口,31个(直接)方法
2. 接口实现
和其他需要显式声明接口实现类 的语言不同,Go语言接口实现是隐式的,不用明确地指出某一个类型实现了某一个接口,只有在某一类型的方法中实现了接口中 全部的方法签名,就意味着此类型实现了这一接口。
如 定义一个图形接口Shape,此接口包含求周长的perimeter方法和求面积的area方法:
1 | type Shape interface { |
任何自定义类型要实现Shape接口都很简单,只需实现Shape内部所有的方法签名即可。
现在创建一个自定义类型Rectangle来标识一个矩形,a,b分别代表长和宽:
1 | type Rectangle struct { |
接着为该类型实现perimeter和area方法:
1 | func (r Rectangle) perimeter() float64 { |
再创建一个自定义类型Rectangle来标识一个三角形,a,b,c分别代表三条边:
1 |
|
也为该类型实现perimeter和area方法:
1 | func (t Triangle) perimeter() float64 { |
实例化一个矩形和三角形,计算其周长和面积:
1 | package main |
执行:
1 | 矩形周长为14.000000,面积为12.000000 |
3. 接口动态类型
一个接口类型的变量,能够接收任何实现了此接口的 (开发人员)自定义的类型。
一般把存储在接口中的类型(如Rectangle,Triangle,即隐式实现Shape中所有方法的结构体) 称为接口的动态类型,而将接口本身的类型称为接口的静态类型(如Shape)
1 | var s Shape |
4. 接口的动态调用
当接口变量(如Shape)中存储了具体的动态类型时,可以调用接口中所有的方法:
1 | var s Shape |
接口动态调用的过程实质上是 调用当前接口动态类型中具体方法的过程。如下,接口动态调用表现出不同动态类型的行为:
1 | var s Shape |
输出为:
1 | 矩形周长为14,面积为12 |
在对接口变量进行动态调用时,调用的方法只能是接口中具有的方法。如Rectangle类型还有另外的方法getHeight,接口变量在调用时,编译不会通过:
1 | var s Shape |
1 | s.getHeight undefined (type Shape has no field or method getHeight) |
5. 多接口
一个类型可以同时实现多个接口,如标准库中src/os/types.go中的
1 | // File represents an open file descriptor. |
其实现了src/io/io.go中的Reader接口和Writer接口
6. 接口的组合
定义的接口可以是其他接口的组合,如理解Go接口的实际应用中提到的标准库src/io/io.go中的ReadWriter,ReadCloser,WriteCloser等几个接口。
以ReadWriter为例,其组合了Reader接口和Writer接口。 当类型实现了ReadWriter时,意味着次类型既可读又可写。
1 | // ReadWriter is the interface that groups the basic Read and Write methods. |
一个方法包含越多的方法,其抽象性就越低,表达的行为就越具体
7. 接口类型断言
更多参考 Golang类型断言
8. 空接口
如果接口中没有任何方法签名,则这是Go中另一类特殊接口,即空接口
空接口可以存储结构体,字符串,整型等任何数据类型,是很多函数的入参,如fmt.Println:
1 | // Println formats using the default formats for its operands and writes to standard output. |
使用 i.(type)获取空接口中的动态类型
i代表接口变量,type是固定关键字,和带方法接口的类型断言不是一回事。
这个语法仅在switch语句中有效,如Println源码中:
1 | // Some types can be done without reflection. |
即 使用switch语句嵌套i.(type)语法,获取空接口中的动态类型,并根据动态类型不同进行不同的格式化输出。
9. 接口的比较性
两个接口之间可以通过 == 或 !=来比较,规则为:
动态值为nil的接口变量相等
如果只有一个接口为nil,则比较结果为false
两个接口不为nil,且接口变量具有相同的动态类型和动态类型值,则两接口相同
如果接口存储的动态类型值是不可比较的,则运行时会报错
原文链接: https://dashen.tech/2016/03/02/golang之interface入门/
版权声明: 转载请注明出处.