什么是接口?
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
~Ttype 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")
}
总结
- 接口可以定义一组类型的集合或是一组方法的集合。
- 通过接口定义的类型约束不同于普通的接口类型。
- 接口类型虽然方便,也要避免过度使用,否则容易导致程序易读性的下降。