共计 2238 个字符,预计需要花费 6 分钟才能阅读完成。
接口
接口是用来定义行为的类型,定义的行为不禁接口间接实现,而由通过办法由定义的类型实现
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.go
package main
import ("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 具备了动静派发的个性,即在调用接口类型的办法时,如果编译期间不能确认接口的类型,则会在运行期间决定具体调用该办法的具体实现
动静派发在构造体上的体现十分差,该当尽量避免应用构造体类型实现接口