Golang 接口类型笔记

Published: 2022-08-27

Tags: Golang

本文总阅读量

什么是接口?

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 intint | 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")
}

总结

  1. 接口可以定义一组类型的集合或是一组方法的集合。
  2. 通过接口定义的类型约束不同于普通的接口类型。
  3. 接口类型虽然方便,也要避免过度使用,否则容易导致程序易读性的下降。

参考