14 interface
14 接口
接口(interface)本质上就是 一组函数的集合,采用隐式实现,无需显式声明,只要一个类型实现了接口的所有方法,就自动实现了该接口。
基本使用
接口的定义格式如下:
type 接口名 interface {
方法名1(参数列表1) 返回值列表1
方法名2(参数列表2) 返回值列表2
...
}
对于接口的命名,建议给接口名加上 er
结尾,然后通过首字母的大小写来控制是否可见,这里我们看一个简单的例子:
type Usber interface {
Start()
Stop()
}
type Phone struct {
Name string
}
func (p Phone) Start() {
fmt.Println(p.Name, "开始工作")
}
func (p Phone) Stop() {
fmt.Println(p.Name, "停止工作")
}
func main() {
var usb Usber
usb = Phone{Name: "小米手机"}
usb.Start() // 小米手机 开始工作
}
很自然就能想到,只要实现指定方法的对象就可以用同一个接口类型来描述,那么把接口类型作为参数就可以实现多态的行为了。
func (c Computer) Work(usb Usber) {
usb.Start()
usb.Stop()
}
func main() {
computer := Computer{}
computer.Work(Phone{Name: "华为手机"})
computer.Work(Camera{})
}
对于一个接口的使用,建议遵守如下规范:
避免过度设计:只有当多个类型需要以相同方式处理时才定义接口
接口应小而精:一个接口包含的方法越少越好,Go 标准库中很多接口只有一个方法
面向接口编程:函数参数优先使用接口类型而非具体类型,提高代码灵活性
空接口
前面我们提到了,只要一个类型实现了接口的所有方法,那就可以用接口类型来描述它,那一个空接口呢,它不包含任何方法,因此 任何类型都自动实现了空接口,从而可以使用空接口来描述所有类型的数据,它也有一个别名 any
。
func main() {
var x interface{} // 或 var x any
x = "Hello"
fmt.Printf("type:%T value:%v\n", x, x)
x = 100
fmt.Printf("type:%T value:%v\n", x, x)
x = true
fmt.Printf("type:%T value:%v\n", x, x)
}
而一个 any 类型能做的事情就比较多了,这里看两个比较常用的:
// map 的值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "张三"
studentInfo["age"] = 18
studentInfo["married"] = false
// 切片元素
var slice = []interface{}{"张三", 20, true, 32.2}
类型断言
空接口存储的值需要通过 类型断言 获取其具体值:
value, ok := x.(T)
如果 x 存储值的类型就是 T,那么取出值并把 ok 置为 true,否则把 value 按照 T 进行零值初始化并把 ok 置为 false。
func main() {
var x any = "Hello"
v, ok := x.(string)
if ok {
fmt.Println("断言成功:", v)
} else {
fmt.Println("断言失败")
}
}
还有一个只能在 switch 中使用的写法,即通过 .(type)
判断类型:
func justifyType(x any) {
switch v := x.(type) {
case string:
fmt.Printf("字符串: %v\n", v)
case int:
fmt.Printf("整数: %v\n", v)
case bool:
fmt.Printf("布尔值: %v\n", v)
default:
fmt.Println("未知类型")
}
}
接口嵌套
接口可以嵌套组合出新接口,类似于结构体:
type SayInterface interface {
Say()
}
type MoveInterface interface {
Move()
}
type Animal interface {
SayInterface
MoveInterface
}
type Cat struct {
Name string
}
func (c Cat) Say() {
fmt.Println("喵喵喵")
}
func (c Cat) Move() {
fmt.Println("猫会动")
}
func main() {
var animal Animal = Cat{Name: "花花"}
animal.Say()
animal.Move()
}
类型约束
Go 1.18 引入泛型后,接口有了新的用途,除了传统的方法集合,还可以定义 类型集合 用于泛型约束:
注意: 这种方式的接口 只能用于泛型约束,不能作为变量类型。
type Number interface {
int | int64 | float64
}
还有一个比较特别的点,即 底层约束,可以表示一种泛用的类型:
type Integer interface {
int // 只接受 int
}
type IntLike interface {
~int // 接受 int 及底层是 int 的类型
}
混合接口
前面我们介绍了接口要么可以全部写方法,要么全部是变量,可不可以既要又要呢,此时我们的混合接口就出来了,不过这种方式不常用,了解一下就好:
type StringLike interface {
~string // 底层类型是 string
String() string // 必须有 String 方法
}
实际开发中,建议一个接口 要么全是方法,要么全是类型,尽量不要混用。