接口
接口是用来定义行为的类型,定义的行为不禁接口间接实现,而由通过办法由定义的类型实现
Golang中,接口是一组办法的签名,是语言中一个重要的组成部分,其目标是通过引入一个中间层与具体的实现进行拆散,达到解耦合的作用,同时暗藏底层实现,缩小关注点
Golang不同于Java,通过隐式实现申明的接口,即只有实现了接口申明中的办法,就是实现了接口,
接口的定义须要应用interface关键字,且在接口中只能定义方法签名,不能蕴含成员变量
基于官网的io包进行剖析:
type Reader interface { Read(p []byte) (n int, err error)}
下面是io包中申明的Reader接口,如果一个类型须要实现Reader接口,那么就仅须要实现Read(p []byte) (n int, err error)
办法,如LimitedReader就实现了Reader接口:
type LimitedReader struct { R Reader N int64}func(l *LimitedReader) Read(p []byte) (n int, err error) { if ;.N <= 0 { return 0, EOF } if int64(len(p)) > l.N { p = p[o:l.N] } n, err := l.R.Read(p) l.N -= int64(n) return}
Golang只会在参数传递、返回参数和变量赋值时对类型是否实现了某个接口进行查看,接口在定义方法时对实现的接受者做限度,所以会有两种形式实现接口:构造体实现和指针实现。
但这两种实现形式不能够同时存在,Go语言的编译器会在构造体类型和指针类型都实现同一个办法时报错“method redeclared”
type Cat struct {}type Duck interface {}func (c Cat) Quack {} // 构造体实现func (c *Cat) Quack {} // 指针实现var d Duck = Cat{} // 构造体初始化var d Duck = &Cat{} // 指针初始化留神:指针实现接口,构造体初始化变量是无奈通过编译的;而构造体实现接口,指针初始化变量能够(Golang在传递参数是值传递的,指针初始化变量时,指针能够隐式地获取到指向的构造体:c.i能够了解成(*c).i)具体了解就是在Golang中,初始化变量后进行办法调用时会产生`值拷贝`:1.对于初始化的指针来说,意味着拷贝的新指针依然与原指针一样,指向一个雷同且惟一的构造体,所以编译器能够隐式通过对变量的解援用(dereference)获取到指针的构造体2.而对于构造体而言,这是拷贝生成了新的构造体,但办法的参数是指针,编译器既不可能创立一个新的指针,即便创立也无奈指向最后调用该办法的构造体具体的例子如Goinaction的代码示例:listing36.gopackage mainimport ( "fmt")type notifier interface { notify()}type user struct { name string email string}func (u *user) notify() { fmt.Printf("Sending user email to %s<%s>)\n", u.name, u.email)}func main() { u := user{"Bill", "bill@email.com"} sendNotification(u)}func sendNotification(n notifier) { n.notify()}认真查看代码就会发现u是一个构造体类型,而notify办法是应用指针接受者实现的,上述代码天然就无奈编译通过
数据结构
Golang依据接口类型是否蕴含一组办法将接口类型分成两类:
应用runtime.iface构造体示意蕴含办法的接口
type iface struct { tab *itab // runtime.itab类型构造体,接口类型的外围组成部分 data unsafe.Pointer // 指向原始数据的指针 }
应用runtime.eface构造体示意不蕴含任何办法的interface{}类型
type eface struct { _type *_type // 指向类型的指针 data unsafe.Pointer // 指向底层数据的指针 }
接口类型不是任意类型
留神:interface{}类型不是任意类型,如果将类型转换成interface{}类型,变量在运行期间的类型也会发生变化,获取变量类型时会失去interface{}
package main
type Test struct {}
func NilOrNot(v interface{}) bool {
return v == nil
}
func main() {
var s *Test fmt.Println(s == nil) fmt.Println(NilorNot(s))
}
运行上述代码时,第一行打印true,第二行会打印false。
呈现上述景象的起因就是在调用NilOrNot函数时产生了隐式的类型转换:*Test类型转换成interface{},除了这种传参的状况,在变量赋值也是如此
动静派发(Dynamic dispatch)是在运行期间抉择具体多态操作(办法或函数)执行的过程,接口的引入使得Golang具备了动静派发的个性,即在调用接口类型的办法时,如果编译期间不能确认接口的类型,则会在运行期间决定具体调用该办法的具体实现
动静派发在构造体上的体现十分差,该当尽量避免应用构造体类型实现接口