概述
接口类型是由 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 推崇基于接口
的组合
思想
通过嵌入
其它已有的接口类型的方式来构建新的接口类型
步骤
不管接口大小,先抽象出接口
将大接口拆分
为小接口
关注接口的单一职责