接口

简单的来说,interface(接口)类型是由一组方法定义的集合。它的含义是:“如果某样东西可以完成这个,那么它就可以用在这里”。其定义的方法也很简单:

type Human interface {
  SayHi()
  SayHello()
}

Human 表示我们定义了一个名为 Humaninterface 类型,{} 里面则是这个 interface 类型所包含的方法。

interface 类型只需要声明其包含哪些方法,不需要负责实现。这些方法由实现方自行实现。因此,interface 类型只定义方法集合,而不关心其具体实现方式

如果实现方具有(实现)该 interface 类型中所定义的所有方法,那么我们就称其实现了这个 interface 类型。

如果一个变量是某个 interface 类型,那么其值只能可以存放实现了这些方法的值。

package main

import "fmt"

type Human interface {
  SayHi()
  SayHello(obj string)
}

type Man struct {
  name string
  age int
}

func (m Man) SayHi() {
  fmt.Println("Hi, I am", m.name)
}

func (m Man) SayHello(obj string) {
  fmt.Println("Hello,", obj)
}

func main() {
  xiaoming := Man{
    name: "xiaoming",
        age: 18,
  }

  fmt.Println(xiaoming.name)  // xiaoming

  xiaoming.SayHi()  // Hi, I am xiaoming
  xiaoming.SayHello("Struct")  // Hello, Struct

  var a Human // 定义变量 a 为 Human interface 类型

  // 因为 Man struct 类型实现了 Human interface 类型中定义的所有方法,
  // 所以我们可以说 Man struct 类型实现了 Human interface 类型。
  // 因此变量 a 可以保存 Man struct 类型的值 —— xiaoming。
  a = xiaoming

  // 执行 Human interface 类型中定义的方法
  // 这里的 a 实际的值为 xiaoming,因此执行 a.SayHi() 和 xiaoming.SayHi() 的结果一样
  a.SayHi() // Hi, I am xiaoming
  a.SayHello("Interface") // Hello, Interface

  xiaodong := Man{
    name: "xiaodong",
  }

  fmt.Println(xiaodong.name)  // xiaodong

  xiaodong.SayHi()  // Hi, I am xiaodong
  xiaodong.SayHello("Struct")  // Hello, Struct

  // xiaodong 也是 Man struct 类型,因此变量 a 也可以保存 xiaodong。
  a = xiaodong

  // 此时,a 的值改为了 xiaodong,因此执行 a.SayHi() 和 xiaodong.SayHi() 的结果一样
  xiaodong.SayHi()  // Hi, I am xiaodong
  xiaodong.SayHello("Interface")  // Hello, Struct
}

interface 可以被任何其他类型实现,当然,一个其他类型对象也可以同时实现多个 interface。我们再来看一个复杂点的例子:

package main

import "fmt"

type Human interface {
  SayHi()
}

type Man interface {
  SayHi()
  PlayGame()
}

type Woman interface {
  SayHi()
  Sing()
}

type superman struct {
  name string
}

func (s superman) SayHi() {
  fmt.Println("Hi, I am superman.")
}

func (s superman) PlayGame() {
  fmt.Println("Hi, I am superman, I can play game.")
}

func (s superman) Sing() {
  fmt.Println("Hi, I am superman, I can sing.")
}

type student struct {
  name string
  age int
}

func (s student) SayHi() {
  fmt.Println("Hi, I am a student.")
}

func (s student) PlayGame() {
  fmt.Println("Hi, I am a student, I can play game, too.")
}

type employee struct {
  name string
}

func (e employee) SayHi() {
  fmt.Println("Hi, I am a employee.")
}

func (e employee) Sing() {
  fmt.Println("Hi, I am a employee, I can not play game, but I can sing.")
}

func main() {
  xiaoming := student{"xiaoming", 18}
  Tom := employee{"Tom"}
  spiderman := superman{"spiderman"}

  var m Man

  m = xiaoming

  m.SayHi() //
  m.PlayGame()

  m = spiderman

  m.SayHi()
  m.PlayGame()

  var w Woman

  w = Tom

  w.SayHi()
  w.Sing()

  w = spiderman

  w.SayHi()
  w.Sing()

  var h Human

  // h = xiaoming
  // h = Tom
  h = spiderman

  h.SayHi()
}

在这个例子中,supermanstudent 类型都实现了 Man interfacesupermanemployee 类型都实现了 Woman interface。同时,这三个 struct 类型又都同时实现了 Human interface

因此,在上面的代码中,m 可以保存 xiaomingspiderman 的值,w 可以保存 Tomspiderman 的值,而 h 可以保存这三个的值。

接口的真正含义

主要注意的是,interface 类型的值只能调用该 interface 类型规定的方法。这也是我们使用 interface 类型的一大好处 —— 屏蔽内部方法。

我们来看一个例子:

package main

import "fmt"

type superman struct {
  name string
}

func (s superman) SayHi() {
  fmt.Println("Hi, I am", s.name)
}

func (s *superman) SetName(name string) {
  s.name = name
  fmt.Println("Hi, I got a new name,", s.name)
}

func beBorn() superman {
  spiderman := superman{}

  spiderman.SetName("spiderman")  // 给这个超人一个名字
  spiderman.SayHi() // Hi, I am spiderman

  fmt.Println("Great, My name is", spiderman.name)  // Great, My name is spiderman

  return spiderman
}

func main() {
  man := beBorn()

  man.SayHi() // Hi, I am spiderman
  fmt.Println(man.name) // spiderman

  man.SetName("Batman") // 注意!这里重新给这位超人设置了一个新名字

  fmt.Println(man.name) // Batman

  man.SayHi() // Hi, I am Batman
}

假如没有 interface,那么我们通过 beBorn() 函数创建一个 superman struct 类型的值,在该函数外也能够使用 superman struct 下的所有方法。

在上面的例子中,我们就通过在 beBorn() 函数外使用 man.SetName("Batman") 给我们心爱的超人换了一个名字。从此 “spiderman” “Batman” 傻傻分不清楚。

超人怎么能随便乱换名字呢,这不就乱套了吗?

要想解决这个问题,我们就需要在超人有了名字之后禁止他再换名字,即在 beBorn() 函数外不能再执行 SetName() 方法。interface 就可以帮我们来达到这个目的。

package main

import "fmt"

type Hero interface {
  SayHi()
}

type superman struct {
  name string
}

func (s superman) SayHi() {
  fmt.Println("Hi, I am", s.name)
}

func (s *superman) SetName(name string) {
  s.name = name
  fmt.Println("Hi, I got a new name,", s.name)
}

func beBorn() Hero {
  spiderman := superman{}

  spiderman.SetName("spiderman")
  spiderman.SayHi()

  fmt.Println("Great, My name is", spiderman.name)

  var h Hero

  h = spiderman

  // 返回一个接口值而非实现的类型。
  // 这里返回 Hero interface 类型。
  return h
}

func main() {
  // man 为 Hero interface 类型
  man := beBorn()

  man.SayHi()

  // Hero interface 类型中没有 SetName 方法,因此无法调用。
  // man.SetName("Batman")
}

接口实现和指针

当我们在实现某个 interface 时,如果方法的接收者为一个指针,那么我们需要特别注意了。例如下面这个例子:

package main

import "fmt"

type Hero interface {
  getName() string
  setName(name string)
}

type superman struct {
  name string
}

func (s superman) getName() string {
  return s.name
}

// 该方法的接收者为指针。
func (s *superman) setName(name string) {
  fmt.Println("通过指针的形式设置 name")

  s.name = name
}

func main() {
  man := superman{}

  var h Hero

  // 因为 setName 方法的接收者为指针,而这里 man 不是指针类型,因此我们就不能说 man 的值实现了 Human interface,程序在编译时会抛出一个错误。
  // h = man

  // 正确的方式是 h 应该存储其对应的指针
  h = &man

  h.setName("spiderman")
}

鸭子类型

我们之前说过,interface 可以被任何其他类型实现。例如,在这个例子中:

package main

import "fmt"

type Human interface {
  SayHello()
}

type Man struct {
  name string
}

func (s Man) SayHello() {
  fmt.Println("Hello, I am", s.name)
}

type Monkey struct {
  name string
}

func (m Monkey) SayHello() {
  fmt.Println("Hello, I am", m.name)
}

func main() {
  p := Man{"xiaoming"}
  m := Monkey{"monkey"}

  var h Human

  h = p

  h.SayHello()

  h = m

  m.SayHello()
}

尽管 ManMonkey 不是同一个类型,但是由于其都实现了 SayHello() 方法,因此用起来是一样的。在编程的世界里,我们成这种为“鸭子类型”。

“鸭子类型”即当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

Go 语言通过 interface 实现了“鸭子类型”。

interface 参数

interface 也可以作为一个函数的参数进行传递,从而达到函数接受任何类型的值作为参数的目的。

package main

import "fmt"

type Stringer interface {
  String() string
}

// 接受 interface 类型,任何实现了该 interface 的类型的值都可以作为参数传递给 log 函数
func log(s Stringer) {
  fmt.Println(s.String())
}

type Human struct {
  name string
}

func (h Human) String() string {
  return "哈哈哈,我是 Human"
}

// 自定义一个 myInt 类型,本质上还是一个 int 类型
type myInt int

func (i myInt) String() string {
  return "哈哈哈,我是 myInt"
}

func main() {
  h := Human{"human"}
  i := myInt(1) // 这里的 myInt(1) 是将 int 类型的值 1 转换为 myInt 类型。类型转换的语法是:T(value)

  log(h)
  log(i)
}

Comma-ok 断言

我们知道 interface 的变量里面可以存储任意实现了该 interface 的类型的值。那么我们怎么知道这个变量里面实际保存了的是哪个类型的对象呢?

Go 语言里面有一个语法,可以用来直接判断是否是该类型的变量:value, ok = element.(T)

elementinterface 变量,T 是被断言的类型。

如果判断成功,这里 value 就是 element 变量的实际值,oktrue bool, 如果失败,value 为类型 T 的“零值”,okfalse bool

package main

import "fmt"

type Human interface {
  SayHi()
}

type Man struct {
  name string
}

func (m Man) SayHi() {
  fmt.Println("Hi, I am a Man")
}

type Woman struct {
  name string
}

func (w Woman) SayHi() {
  fmt.Println("Hi, I am a Woman")
}

func main() {
  m := Man{"man"}
  w := Woman{"woman"}

  var h Human

  h = m

  // 条件成立
  if value, ok := h.(Man); ok {
    fmt.Println("1. h 的值是 Man 类型")
    fmt.Println(value)
  }

  // 条件不成立
  if value, ok := h.(Woman); ok {
    fmt.Println("1. h 的值是 WoMan 类型")
    fmt.Println(value)
  }

  h = w

  // 条件不成立
  if value, ok := h.(Man); ok {
    fmt.Println("2. h 的值是 Man 类型")
    fmt.Println(value)
  }

  // 条件成立
  if value, ok := h.(Woman); ok {
    fmt.Println("2. h 的值是 WoMan 类型")
    fmt.Println(value)
    fmt.Println(value == WoMan)
  }
}

#

要判断一个 interface 的值的类型,除了使用 Comma-ok 之外,还可以使用 switch 语法:

package main

import "fmt"

type Human interface {
    SayHi()
}

type Man struct {
    name string
}

func (m Man) SayHi() {
    fmt.Println("Hi, I am a Man")
}

type Woman struct {
    name string
}

func (w Woman) SayHi() {
    fmt.Println("Hi, I am a Woman")
}

func main() {
    m := Man{"man"}
    var h Human

    h = m

    // 这里使用的是 value := element.(type) 语法
    switch value := h.(type) {
    case Man:
        // 条件成立
        fmt.Println("Man 类型", value)
    case Woman:
        fmt.Println("Woman 类型", value)
    }
}

需要强调的是,element.(type) 语法不能在 switch 以外的任何逻辑里面使用,如果你要在 switch 外面判断一个类型就使用 comma-ok

results matching ""

    No results matching ""