接口

120 阅读4分钟

1.接口的定义

定义一个接口往往是从它所需要的函数为入口的,说人话就是【规定几个函数放在一起形成新整体】
比如:我有三个函数,gaga(),walk(),swimming(),那就可以规定它们3个放一起形成一个接口

// Duck 定义接口
// 接口典型的一个特点就是,多个功能【函数】的集合
type Duck interface {
   gaga()
   walk()
   swimming()
}

2.不得不提到的鸭子类型

这个概念的名字来源于James Whitcomb Riley提出的鸭子测试:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

从这句著名的话中,我们可以看出,符合【鸭子走】【鸭子游】【鸭子叫】的鸟可以叫它鸭子

所以反映的就是,【鸭子走】【鸭子游】【鸭子叫】3个功能形成了 Duck 这个接口

【那具有功能】这个意思,用代码怎么体现呢,答案是,给结构体绑定函数

package main

import "fmt"

// Duck 定义接口
type Duck interface {
   gaga()
   walk()
   swimming()
}

type bird struct{}

func (b *bird) gaga() {
   fmt.Println("ga ga ga")
}
func (b *bird) walk() {
   fmt.Println("像鸭子走路")
}
func (b *bird) swimming() {
   fmt.Println("像鸭子游泳")
}

func main() {
   //鸭子类型【接口】,指的是你的结构体只要绑定了鸭子类型【接口】的所有方法,那么就可以说你这个结构体是鸭子类型【接口】
   //所以按照这个理论,要是鸭子类型【里面没有方法】,那么所有的结构体就都可以说自己是一个鸭子类型【接口】
   //从而一切结构体都能赋值给空接口
   //bird结构体是绑定了三个方法的,所以就可以说bird是一个Duck类型【接口】
   var d Duck = &bird{} //结构体指针
   d.walk()
   d.gaga()
   d.swimming()
}

3.结构体 与 接口的多对多关系

一个结构体可以实现多个接口,多个结构体也可以对应一个接口

type powerDuck interface {
   charge() //可以充电的是电动鸭子
}
type rubberDuck interface {
   swimming() //可以游泳的是橡皮鸭子
}

// redDuck绑定了,charge + swimming两个方法
// 所以redDuck既可以说它是电动鸭子,也可以说它是橡皮鸭子
// 这就是一个结构体实现了多个鸭子类型【接口】
type redDuck struct{}

func (r *redDuck) charge() {
   fmt.Println("红色鸭子可以充电,所以它是电动鸭子")
}
func (r *redDuck) swimming() {
   fmt.Println("红色鸭子可以游泳,所以红色鸭子是橡皮鸭子")
}

type yellowDuck struct{}

// yellowDuck也实现了powerDuck接口
// 所以加上redDuck,这就是多个结构体实现了同一个powerDuck鸭子类型【接口】
func (y *yellowDuck) charge() {
   fmt.Println("黄色鸭子可以充电,所以它是电动鸭子")
}

4.接口嵌套

package main

import "fmt"

type powerDuck interface {
   charge() //可以充电的是电动鸭子
}
type rubberDuck interface {
   swimming() //可以游泳的是橡皮鸭子
}
type supperDuck interface {
   powerDuck
   rubberDuck
   fly()
}

type redDuck struct{}

func (r *redDuck) charge() {
   fmt.Println("红色鸭子可以充电,所以它是电动鸭子")
}
func (r *redDuck) swimming() {
   fmt.Println("红色鸭子可以游泳,所以红色鸭子是橡皮鸭子")
}
func (r *redDuck) fly() {
   fmt.Println("红色鸭子可以飞,所以红色鸭子是飞行鸭子")
}
func main() {
   //有一个问题,各个函数前面都绑定了指针,那可不可以绑定普通结构体变量呢??
   //答案是可以:区别是,绑定指针,那么下面的s赋值就必须是 &redDuck{}
   //如果绑定普通结构体变量,那么下面的s赋值就是redDuck{} 或者 &redDuck{}
   //这里的本质就是值传递和指针传递,值传递会用副本,而指针就是源数据
   var s supperDuck = &redDuck{}
   s.charge()
   s.swimming()
   s.fly()
}

5.结构体嵌套接口变量【实现解耦效果】

    package main

    import "fmt"

    type duck interface {
       gaga()
       walk()
    }

    type chicken struct {
       duck
    }

    func (b *chicken) gaga() {
       fmt.Println("chicken gaga")
    }

    type bird struct{}

    func (s *bird) gaga() {
       fmt.Println("bird gaga")
    }
    func (s *bird) walk() {
       fmt.Println("bird walk")
    }

    type goose struct{}

    func (g *goose) gaga() {
       fmt.Println("goose gaga")
    }
    func (g *goose) walk() {
       fmt.Println("goose walk")
    }
    func main() {

       c := chicken{duck: &bird{}}
       cc := chicken{duck: &goose{}}
       c.gaga() //chicken原本只绑定了gaga()

       //由于chicken肚子里面嵌套了duck接口,所以它可以通过实现了duck接口的任何结构体调walk
       //并且传进来的结构体不一样,walk出来也不一样===这就是解耦
       c.duck.gaga()
       c.duck.walk() //相当于c.walk()

       cc.duck.gaga()
       cc.duck.walk() //相当于cc.walk()

    }
    //chicken gaga
    //bird gaga
    //bird walk
    //goose gaga
    //goose walk

6.给万能的接口切片赋值也有坑

package main

import "fmt"

// MyPrint :我们在用别人的库的时候,经常遇到下面这种函数
// 就是可以给【入参】传任意值的函数,data就相当于一个interface的切片
func MyPrint(data ...interface{}) {
   for _, v := range data {
      fmt.Println(v)
   }
}
func main() {
   //那么真的是可以传万物吗??
   //data1就是一个接口切片,里面可以放万物的
   data1 := []interface{}{
      "xhz", "hello", 100,
   }
   MyPrint(data1...)
   data2 := []string{
      "a", "b", "c",
   }
   //MyPrint(data2...)发现编辑就会报错,不过是把interface特殊化成了string吗
   //结果就不支持传数据了,这和我们理解的【装万物】有点不一样啊
   //怎样解决呢??思路是,既然支持interface切片,那就提前把string切片的数据拿出来放在interface切片里

   var data3 []interface{}
   for _, v := range data2 {
      data3 = append(data3, v)
   }
   MyPrint(data3...)

}