Go 语言里有非常灵活的 接口 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 说明 对象的行为。接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
type Namer interface {Method1(param_list) return_typeMethod2(param_list) return_type...}
按照约定,只包含一个方法的接口的名字由方法名加 [e]r 后缀组成,例如 Printer、Reader、Writer、Logger、Converter 等等。还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 .NET 或 Java 中那样)。
Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。
Go 语言中接口可以有值,一个接口类型的变量或一个 接口值 :var ai Namer,ai 是一个多字(multiword)数据结构,它的值是 nil。它本质上是一个指针,虽然不完全是一回事。指向接口值的指针是非法的,它们不仅一点用也没有,还会导致代码错误。
类型(比如结构体)实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型的:即实现接口,同时方法集也构成了该类型的接口。实现了 Namer 接口类型的变量可以赋值给 ai (接收者值),此时方法表中的指针会指向被实现的接口方法。当然如果另一个类型(也实现了该接口)的变量被赋值给 ai,这二者(指针和方法实现)也会随之改变。
- 类型不需要显式声明它实现了某个接口:接口被隐式地实现。
- 多个类型可以实现同一个接口。
- 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
- 一个类型可以实现多个接口。
- 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。
package mainimport "fmt"type Shaper interface {Area() float32}type Square struct {side float32}func (sq *Square) Area() float32 {return sq.side * sq.side}func main() {sq1 := new(Square)sq1.side = 5var areaIntf ShaperareaIntf = sq1// shorter,without separate declaration:// areaIntf := Shaper(sq1)// or even:// areaIntf := sq1fmt.Printf("The square has area: %f\n", areaIntf.Area())}
1、接口嵌套接口
一个接口可以包含一个或多个其他的接口。 ```go type ReadWrite interface { Read(b Buffer) bool Write(b Buffer) bool }
type Lock interface { Lock() Unlock() }
type File interface { ReadWrite Lock Close() }
<a name="Qj13A"></a># 2、类型断言只有接口类型的变量才有类型断言。一个接口类型的变量 `varI` 中可以包含任何类型的值,必须有一种方式来检测它的 **动态** 类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它总是可以分配给接口变量本身的类型。通常我们可以使用 **类型断言** 来测试在某个时刻 `varI` 是否包含类型 `T` 的值。```gov := varI.(T) // unchecked type assertion
类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败会导致错误发生。更安全的方式是使用以下形式来进行类型断言:
if v, ok := varI.(T); ok { // checked type assertionProcess(v)return}// varI is not of type T
如果转换合法,v 是 varI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,ok 是 false,也没有运行时错误发生。
3、类型判断
接口变量的类型也可以使用一种特殊形式的 switch 来检测:type-switch
switch t := areaIntf.(type) {case *Square:fmt.Printf("Type Square %T with value %v\n", t, t)case *Circle:fmt.Printf("Type Circle %T with value %v\n", t, t)case nil:fmt.Printf("nil value: nothing to check?\n")default:fmt.Printf("Unexpected type %T\n", t)}
变量 t 得到了 areaIntf 的值和类型, 所有 case 语句中列举的类型(nil 除外)都必须实现对应的接口。
可以用 type-switch 进行运行时类型分析,但是在 type-switch 不允许有 fallthrough 。
如果仅仅是测试变量的类型,不用它的值,那么就可以不需要赋值语句。
switch areaIntf.(type) {case *Square:// TODOcase *Circle:// TODOdefault:// TODO}
4、空接口
空接口不包含任何方法,它对实现不做任何要求。
type Any interface {}
任何其他类型都实现了空接口,any 或 Any 是空接口一个很好的别名或缩写。
空接口类似 Java/C# 中所有类的基类: Object 类,二者的目标也很相近。
package mainimport "fmt"var i = 5var str = "ABC"type Person struct {name stringage int}type Any interface{}func main() {var val Anyval = 5fmt.Printf("val has the value: %v\n", val)val = strfmt.Printf("val has the value: %v\n", val)pers1 := new(Person)pers1.name = "Rob Pike"pers1.age = 55val = pers1fmt.Printf("val has the value: %v\n", val)switch t := val.(type) {case int:fmt.Printf("Type int %T\n", t)case string:fmt.Printf("Type string %T\n", t)case bool:fmt.Printf("Type boolean %T\n", t)case *Person:fmt.Printf("Type pointer to Person %T\n", t)default:fmt.Printf("Unexpected type %T", t)}}
