什么是接口?
type Runner interface {
Run()
}
通过以上代码,可以定义一个名为 Runner
的接口,它定义了一个方法 Run()
,当我们说 XX 类型实现某个接口的时候,也就证明 XX 类型实现了其中所定义的方法。
定义一个名为 Program
类型,同时这个类型实现了 Run()
方法,就可以这样说:“Program 类型实现了 Runner 方法”。
type Program struct {
/* fields */
}
func (p Program) Run() {
/* running */
}
func (p Program) Stop() {
/* stopping */
}
实现了接口的类型能够赋值到这个接口的变量
var runner Runner
runner = Program{}
runner.Run()
通过接口,我们能够对程序进行抽象和解耦。这个链接( Go 语言接口的优势?)可以了解更多接口的用途
那么,Interface{}
是什么?
type Empty interface{}
var a Empty
a = 42
a = "hello"
a = struct{}{}
我们在代码中随处可见 interface{}
,例如函数参数定义为 interface{}
类型,就能接收任意的类型。
如果熟悉 C 语言,它或多或少跟 void *
类似,可以指向任何你需要的值。
有了最开始 Runner
示例,有助于了解此处的 interface{}
,代码中定义了一个 Empty
接口,这个接口内没有任何内容,是一个空接口,也就是说,无论什么类型,都“满足” Empty
接口的要求,默认实现了 Empty
接口。
也解释了我在刚开始学习 Golang 时候,疑惑定义变量时为什么是 interface{}
,后边带着一个 "{}",而不是像其它类型一样简单写作 interface
。
学习到这里,提到 Interface,它代表方法的集合。
打破沉寂从 1.18 开始,引入泛型(Generic)后,interface 被赋予了新的使命。
编写结构如下,它包含了一组类型,用作 “类型约束”
type IntOrString interface {
int | string
}
// 因为存在 IntOrString 约束,x 值可以为 int 或 string 类型
func Foo[T IntOrString](x T) {
fmt.Println(x)
}
引用官方文档一段描述
- The syntax for Interface types now permits the embedding of arbitrary types (not just type names of interfaces) as well as union and
~T
type elements. Such interfaces may only be used as type constraints. An interface now defines a set of types as well as a set of methods.
翻译:Interface 类型的语法现在允许内嵌任意的类型(不仅仅是类型的名字),以及 union
和 ~T
类型的元素。这样的接口仅能作为类型约束使用,现在一个接口可以定义一组类型的集合或是一组方法的集合。
重点:类型约束不能当类型使用。
package main
import (
"fmt"
)
type IntOrString interface{
int | string
}
func main() {
var a IntOrString = 1 // 错误,类型约束不能当类型使用。
fmt.Printf("%v\n", a)
}
// 编译报错
// Error: interface contains type constraints
PS:不排除后续版本支持类型约束能像普通的接口类型一样使用。
Permitting constraints as ordinary interface types
This is a feature we are not suggesting now, but could consider for later versions of the language.
另外,我们自己定义的类型 type Int int
, int | string
约束是不支持的
package main
import (
"fmt"
)
type IntOrString interface{int | string}
type Slice[T IntOrString] []T
type Int int
func main() {
var a Slice[Int] = []Int{1, 2, 3} // 错误,int | string 约束不支持 Int 类型
fmt.Printf("%v\n", a)
}
// Error
// Int does not implement IntOrString (possibly missing ~ for int in constraint IntOrString)
可以修改约束如下
// 在类型前加一个 ~
type IntOrString interface{ ~int | string}
接口与多态
根据子类的类型去执行子类当中的方法,用相同的调用方式调出不同结果或者是功能的情况,这种情况就叫做多态。
以下示例,Dog 结构体和 Cat 结构体都实现了 say()
方法,所以它们能够赋值给 Mammal 接口,当调用 Mammal 接口变量的 say()
方法时,会到对应的类型中调用同名方法执行。
package main
import (
"fmt"
)
type Mammal interface {
say()
}
type Dog struct {
name string
}
func (d Dog) say() {
fmt.Printf("I am %s \n", d.name)
}
type Cat struct {
name string
}
func (c Cat) say() {
fmt.Printf("I am %s \n", c.name)
}
func main() {
var m Mammal
m = Dog{ name: "dog" }
m.say()
m = Cat{ name: "cat" }
m.say()
}
结果
➜ go run main.go
I am dog
I am cat
可以看到在 Golang 中,接口和实现是解耦的,隐示进行了绑定。
再看一个示例,通过多态,我们能够定义定义一批过滤器,统一进行处理。
package main
import "fmt"
type Filter interface {
Process([]int) []int
}
type UniqueFilter struct{}
func (UniqueFilter) Process(inputs []int) []int {
// 返回去重后的数据
}
type MultipleFilter int
func (mf MultipleFilter) Process(inputs []int) []int {
// 返回过滤后的数据
}
func main() {
numbers := []int{12, 7, 21, 12, 12, 26, 25, 21, 30}
filters := []Filter{
UniqueFilter{},
MultipleFilter(2),
MultipleFilter(3),
}
for _, fltr := range filters {
numbers = fltr.Process(numbers)
}
}
接口与反射
当我们将数据结构赋值给接口类型,可以通过两种方式再获取到数据的原始类型。
第一种方式通过断言判断
var m Mammal = Dog{ name: "dog" }
// 方式一
v, ok := m.(Dog)
fmt.Println(v, ok) // {dog}, true
v2, ok2 := m.(Cat)
fmt.Println(v2, ok2) // nil, false
// v3 := m.(Cat) // panic
// fmt.Println(v3)
第二种方式通过 Switch 语句判断
// 方式二
switch v := m.(type) {
case Cat:
v.say()
case Dog:
v.say()
default:
fmt.Println("default")
}
总结
- 接口可以定义一组类型的集合或是一组方法的集合。
- 通过接口定义的类型约束不同于普通的接口类型。
- 接口类型虽然方便,也要避免过度使用,否则容易导致程序易读性的下降。