1. <br />简介<br /> <br /> go语言中的接口类型`interface{}`,是go语言对面向接口编程思想的实现。面向接口的编程方式又被称为鸭子类型。所谓鸭子类型,是对一种抽象模式的概括。go语言中的接口数据类型可以看成是一组抽象方法的集合,而接口数据类型作为这组抽象方法的标签。

    1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1644839312645-d49be659-483b-4b40-a71e-7e869c33e2a3.png#clientId=ubba203ee-bd5b-4&from=paste&height=329&id=ua309bd93&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=617&originalType=binary&ratio=1&size=547874&status=done&style=none&taskId=ub28cde2b-b6a6-40ed-a9ee-3be7703068f&width=463)![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1644839403877-088b5e94-6807-496f-b981-8ab516843f62.png#clientId=ubba203ee-bd5b-4&from=paste&height=226&id=u6ce234f6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=301&originWidth=700&originalType=binary&ratio=1&size=84468&status=done&style=none&taskId=u44ac41f7-58ae-4029-b072-d7a3903ea84&width=525)![image.png](https://cdn.nlark.com/yuque/0/2022/png/25362645/1644847716929-3a0d1f1f-30d1-4cdd-8a43-2749ec304a0a.png#clientId=ue2020c50-712f-4&from=paste&height=319&id=udf193a73&margin=%5Bobject%20Object%5D&name=image.png&originHeight=319&originWidth=445&originalType=binary&ratio=1&size=282662&status=done&style=none&taskId=uad0b08a2-0d2b-488e-a059-542919a9a0a&width=445)

    内容包括

    ○ 介绍鸭子类型
    ○ 多态在python语言中的两种实现方法。
    ○ 鸭子类型在go语言中通过接口实现
    ○ 空接口
    ○ go语言内置接口简介
    ○ go语言接口数据类型的底层实现
    ○ go语言接口数据类型的注意点


    鸭子类型(ducking typing)

    1. “当你看到一个东西走起来像鸭子,游泳起来鸭子,叫起来也像鸭子,那么这个东西就被可以被称为鸭子”

    鸭子类型,可以看成是多态思想的一种形式,我们并不心具体对象的属性或者数据类型,而是重点关心对象的行为,也就是对象包含的方法,我们将多种对象的相似动作抽象出来,形成一个抽象层的接口。

    python使用继承实现多态

    1. class Duck():
    2. a=1
    3. def who(self):
    4. print("I am an Duck")
    5. class PsyDuck(Duck):
    6. '''可达鸭'''
    7. def who(self):
    8. print("我是可达鸭")
    9. class DonaldDuck(Duck):
    10. '''唐老鸭'''
    11. def who(self):
    12. print("我是唐老鸭")
    13. class LittleDuck(Duck):
    14. '''小黄鸭'''
    15. def who(self):
    16. print("我是小黄鸭")
    17. if __name__ == "__main__":
    18. duck1=PsyDuck()
    19. duck2=DonaldDuck()
    20. duck3=LittleDuck()
    21. duck1.who()
    22. duck2.who()
    23. duck3.who()
    24. output:
    25. 我是可达鸭
    26. 我是唐老鸭
    27. 我是小黄鸭

    可以看到,三个对象都继承了同一父类,并重写了父类Who()方法,这就是多态。

    其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,如下


    python用鸭子类型实现多态

    1. def func(obj): //定义一个函数接口,参数为类对象
    2. obj.who() //调用类对象的方法
    3. class PsyDuck:
    4. '''可达鸭'''
    5. def who(self):
    6. print("我是可达鸭")
    7. class DonaldDuck:
    8. '''唐老鸭'''
    9. def who(self):
    10. print("我是唐老鸭")
    11. class LittleDuck:
    12. '''小黄鸭'''
    13. def who(self):
    14. print("我是小黄鸭")
    15. if __name__ == "__main__":
    16. duck1=PsyDuck()
    17. duck2=DonaldDuck()
    18. duck3=LittleDuck()
    19. func(duck1)
    20. func(duck2)
    21. func(duck3)
    22. output:
    23. 我是可达鸭
    24. 我是唐老鸭
    25. 我是小黄鸭

    通过对比python实现多态的两种方式,就可以体会到鸭子类型的思想。和第一方式不同,第一种方式是显示的继承,继承之后自然具备了父类对象的方法,不同子类对父类的方法进行重写,就可以实现多态,而鸭子类型的多态,只需要对象实现了接口定义的所有方法,即可使用该接口函数进行调用,接口函数不关心对象的属性,数据类型,只要实现了接口的所有方法,即可实现多态。

    接下来看如何在go语言中通过接口实现多态


    1. package main
    2. import "fmt"
    3. type Duck interface {
    4. who()
    5. }
    6. type PsyDuck struct {
    7. Name string
    8. }
    9. func (d PsyDuck) who() {
    10. fmt.Println("i am psyDuck")
    11. }
    12. type LittleDuck struct {
    13. Name string
    14. }
    15. func (d LittleDuck) who() {
    16. fmt.Println("i am littleDuck")
    17. }
    18. func GuessWhoIam(d Duck) {//通过参数为接口函数进行调用
    19. d.who()
    20. }
    21. func main() {
    22. var duck Duck
    23. var psyDuck PsyDuck
    24. var littleDuck LittleDuck
    25. duck = psyDuck
    26. duck.who()
    27. duck = littleDuck //对接口重新赋值
    28. duck.who()
    29. GuessWhoIam(psyDuck)//调用可达鸭的who方法
    30. GuessWhoIam(littleDuck)//调用小黄鸭的的who方法
    31. }
    32. output:
    33. i am psyDuck
    34. i am littleDuck
    35. i am psyDuck
    36. i am littleDuck

    在看一个例子,实现音乐播放器,在实现一个收音机,它们都具备播放音乐的功能,如果没有定义统一接口,就会出现下列情形。

    image.png

    为了避免重复造轮子,实现多态的特性。所以进行如下修改,

    1. type Player interface{ //定义统一的播放接口
    2. Play()
    3. Stop()
    4. }
    5. func playList(device magazine.Player,songs []string){
    6. ...
    7. }

    此时只要实现了 Player 接口定义的 Play() 及Stop()方法的对象,都可以作为参数传递进 playList()函数,并调用对象各自的方法了。

    如下图所示:接口的本质是引入一个新的中间层,调用方可以通过接口与具体实现分离,解除上下游的耦合,上层的模块不再需要依赖下层的具体模块,只需要依赖一个约定好的接口。

    image.png


    空接口

    image.png


    内置接口
    error接口

    1. type error interface{
    2. Error() string
    3. }

    自定义的error数据类型如果具有一个返回string的Error方法,它就满足error接口,

    1. package main
    2. import "fmt"
    3. type ComedyError string
    4. func (c ComedyError) Error() string {
    5. return string(c)
    6. }
    7. func main() {
    8. var err error
    9. err = ComedyError("test")
    10. fmt.Println(err)
    11. }

    error类型像int或者string一样是一个“预定义标识符”,它不属于任何包。它是“全局块”的一部分,这意味着它在任何地方可用,不用考虑当前包信息。

    1. package main
    2. import (
    3. "fmt"
    4. "log"
    5. )
    6. type overHeatError float64
    7. func (o overHeatError) Error() string {
    8. return fmt.Sprintf("overheating by %0.2f degree", o)
    9. }
    10. func checkTemperature(actual float64, safe float64) error {
    11. excess := actual - safe
    12. if excess > 0 {
    13. return overHeatError(excess)
    14. }
    15. return nil
    16. }
    17. func main() {
    18. var err error = checkTemperature(127, 100)
    19. if err != nil {
    20. log.Fatal(err)
    21. }
    22. }

    Stringer接口

    1. type Stringer interface{
    2. String() string
    3. }//使用该接口自定义输出的样式
    1. package main
    2. import "fmt"
    3. type Gallon float64
    4. func (g Gallon) String() string {
    5. return fmt.Sprintf("%0.2f gal", g)
    6. }
    7. type Liter float64
    8. func (l Liter) String() string {
    9. return fmt.Sprintf("%0.2f L", l)
    10. }
    11. func main() {
    12. fmt.Println(Gallon(12.00))
    13. fmt.Println(Liter(10.0))
    14. }

    类型断言


    接口具体底层原理。
    https://www.bookstack.cn/read/aceld-golang/4%E3%80%81interface.md