接口类型
Go 语言中的接口就是一组方法的签名,它是 Go 语言的重要组成部分,使用接口能够让我们更好地组织并写出易于测试的代码
在计算机科学中,接口是计算机系统中多个组件共享的边界,不同的组件能够在边界上交换信息;接口的本质就是引入一个新的中间层,调用方可以通过接口和具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口
隐式接口
定义接口需要使用 interface 关键字,在接口中我们只能定义方法签名,不能包含成员变量
type error interface {
Error () string
}
如果一个类型需要实现 error 接口,那么它只需要实现 Error() string 方法,下面的 RPCError 结构体就是 error 接口的一个实现
type RPCError struct {
Code int64
Message string
}
func (e *RPCError) Error() string {
return fmt.Sprintf("%s, code=%d", e.Message, e.Code)
}
接口类型检查
func main() {
// 将 *RPCError 类型的变量赋值给 error 类型的变量 rpcErr
var rpcErr error = NewRPCError(400, "unknown err")
// 将 *RPCError 类型的变量 rpcErr 传递给签名中参数类型为 error 的 AsErr 函数
err := AsErr(rpcErr)
println(err)
}
func NewRPCError(code int64, msg string) error {
// 将 *RPCError 类型的变量从函数签名的返回值类型为 error 的 NewRPCError 函数中返回
return &RPCError{
Code: code,
Message: msg,
}
}
func AsErr(err error) error {
return err
}
类型转换
package main
func main() {
type Test struct{}
v := Test{}
Print(v)
}
func Print(v interface{}) {
println(v)
}
上述函数只接受 interface{} 类型的值,在调用 Print 函数时会将参数 v 进行类型转换,将原来的 Test 类型转换成 interfacce() 类型
指针和接口
type Cat struct {}
type Duck interface {
Quack()
}
func (c Cat) Quack {} // 使用结构体实现接口
func (c *Cat) Quack {} // 使用结构体指针实现接口
var d Duck = Cat{} // 使用结构体初始化变量
var d Duck = &Cat{} // 使用结构体指针初始化变量
| \ | 结构体实现接口 | 结构体指针实现接口 |
|---|---|---|
| 结构体初始化变量 | 通过 | 不通过 |
| 结构体指针初始化变量 | 通过 | 通过 |
分析: Go 语言在传递参数时都是传值的 对于 &Cat{} 来说,意味着拷贝了一个新的 &Cat{} 指针,该指针与原来的指针指向一个相同且唯一的结构体,所以编译器可以隐式的对变量解引用(dereference)获取指针指向的结构体 对于 Cat{} 来说,意味着 Quack 方法会接受一个全新的 Cat{} ,因为方法的参数是 *Cat,编译器不会无中生有创建一个新的指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用该方法的结构体
动态派发
动态派发(Dynamic dispatch)是在运行期间选择具体多态操作(方法或者函数)执行的过程,它是一种在面向对象语言中常见的特性。Go 语言对接口的引入带来了动态派发这个特性 在调用接口类型的方法时,如果编译期间不能确认接口的类型,Go 语言会在运行期间决定具体调用该方法的哪个实现
func main() {
var c Duck = &Cat{Name: "grooming"}
// 以 Duck 接口类型的身份调用,调用时需要经过运行时的动态派发
c.Quack()
// 以 *Cat 具体类型的身份调用,编译期就会确定调用的函数
c.(*Cat).Quack()
}