概述
接口类型是由 type 和 interface 关键字定义的一组方法集合(唯一确定所代表的接口) 称为方法,而非函数,更多是从这个接口的实现者的角度考虑的
1 2 3 4 type MyInterface interface { M1(int ) error M2(io.Writer, ...string ) }
方法的参数列表中的形参名和返回值列表中的具名返回值,都不作为区分两个方法的凭据
1 2 3 4 5 type MyInterface interface { M1(a int ) error M2(w io.Writer, args ...string ) }
在接口类型中声明的方法必须是具名的,且方法名在这个接口类型的方法集合中是唯一的
类型嵌入:在 Go 1.14 开始,接口类型允许嵌入不同接口类型的方法集合存在交集 但需要方法名和函数签名也要保持一致,否则会编译报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type I1 interface { M1() } type I2 interface { M1() M2() } type I3 interface { I1 I2 M3() }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type I1 interface { M1() } type I2 interface { M1(string ) M2() } type I3 interface { I1 I2 M3() }
在接口类型的方法集合中放入首字母小写的非导出方法也是合法的 - 极少使用 如果接口类型的方法集合中包含非导出方法,那么这个接口类型自身通常也是非导出的,仅限于包内使用
context/context.go 1 2 3 4 type canceler interface { cancel(removeFromParent bool , err error ) Done() <-chan struct {} }
空接口类型 - 方法集合为空 通常不需要显式定义空接口类型,而是直接使用 interface{} 的类型字面量
1 type EmptyInterface interface {}
接口类型被定义后,可以用于声明变量,称为接口类型变量,如果没有显式赋予初值,默认值为 nil
1 2 3 4 5 var err error var r io.Reader fmt.Printf("%#v\n" , err) fmt.Printf("%#v\n" , r)
类型 T 实现接口类型 I
类型 T 的方法集合是接口类型 I 的方法集合的等价集合或者超集
此时,类型 T 的变量可以作为合法的右值赋值给接口类型 I 的变量
任何类型都实现了空接口类型的方法集合(空),即可以将任何类型的值作为右值,赋值给空接口类型的变量
1 2 3 4 5 6 7 var i interface {} = 15 i = "hello" type T struct {}var t Ti = t i = &t
类型断言
按 T 为接口类型和非接口类型,语义是不一样的
.(非接口类型)
.(接口类型)
非接口类型
语义:断言存储在接口类型变量 i 中的值的类型为 T
如果断言失败,则 ok 为 false,而 v 为类型 T 的零值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var a int64 = 13 var i interface {} = a v1, ok := i.(int64 ) fmt.Printf("%v, %T, %v\n" , v1, v1, ok) v2, ok := i.(string ) fmt.Printf("%v, %T, %v\n" , v2, v2, ok) v3 := i.(int64 ) fmt.Printf("%v, %T\n" , v3, v3) v4 := i.([]int ) fmt.Printf("%v, %T\n" , v4, v4)
接口类型
语义:断言 i 的值实现了接口类型 T
如果断言成功,v 的类型为 i 的值的类型(更广),并非 T 如果断言失败,v 的类型为 T(更窄),值为 nil
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 type I interface { M() } type T int func (T) M() { println ("T's M" ) } func main () { var t T var i interface {} = t v1, ok := i.(I) if !ok { panic ("the interface value is not of type I" ) } v1.M() fmt.Printf("%T\n" , v1) i = int64 (13 ) v2, ok := i.(I) fmt.Printf("%T, %v\n" , v2, ok) v2 = 13 }
type switch 是接口类型的类型断言的变种
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 type Animal interface { shout() string } type Dog struct {}func (d Dog) shout() string { return "Woof!" } type Cat struct {}func (c Cat) shout() string { return "Meow!" } func main () { var animal Animal = Dog{} switch a := animal.(type ) { case nil : fmt.Println("nil" , a) case Dog, Cat: fmt.Println(reflect.TypeOf(a), a.shout()) default : fmt.Println("unknown" ) } }
小接口
接口类型的背后,是通过把类型的行为抽象成契约,降低双方的耦合程度
隐式契约,自动生效
接口类型和实现者之间的关系是隐式的,无需显式声明 implements
实现者只需要实现接口方法集合中的全部方法,立即生效
使用小契约
尽量定义小接口,方法个数控制在 1~3
接口越大,抽象程度只会越弱
builtin/builtin.go 1 2 3 type error interface { Error() string }
io/io.go 1 2 3 type Reader interface { Read(p []byte ) (n int , err error ) }
net/http/server.go 1 2 3 4 5 6 7 8 9 type Handler interface { ServeHTTP(ResponseWriter, *Request) } type ResponseWriter interface { Header() Header Write([]byte ) (int , error ) WriteHeader(statusCode int ) }
优势
接口越小(方法集合小),抽象程度越高(对应的集合空间就越大),极限情况为 interface{}
易于实现和测试
职责单一,易于复用组合
Go 推崇基于接口的组合思想
通过嵌入其它已有的接口类型的方式来构建新的接口类型
步骤
不管接口大小,先抽象出接口
将大接口拆分为小接口
关注接口的单一职责