14 接口

  • Interface 定义了一个对象的行为规范
  • 只定义规范而不实现,由具体的对象来实现规范的细节

接口类型

  • `Interface ·是一种抽象的类型
  • Interface 是一组 method的集合,是 duck-type programming的一种体现
  • 接口的作用:定义一个协定(规则),
    • 不用关心其数据,和方法具体是什么
  • `Interface· 是一种类型

为什么使用接口

  • ```go
    type Cat struct{}

    func (c Cat) Say() string { return “喵喵喵” }

    type Dog struct{}

    func (d Dog) Say() string { return “汪汪汪” }

    func main() {

    c := Cat{}
    fmt.Println("猫:", c.Say())
    d := Dog{}
    fmt.Println("狗:", d.Say())
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    * 共同特点:都会叫

    * 有重复代码
    * 为了解决 有共同特点而导致的代码重复,设计了接口
    * 接口是一种抽象的类型,

    * 看到一个接口,只知道通过它的方法能做什么

    # 接口的定义

    * Go 提倡面向接口编程
    * 每个接口由数个方法组成,定义格式:

    * ```go
    type 接口类型名 interface{
    方法名1(参数列表) 返回值列表
    方法名2(参数列表) 返回值列表
    }
    • 接口名:自定义的类型名

      • 一般命名时,在单词后 er , 接口名最好能突出该接口的类型含义
    • 方法名:当接口名首字母是大写,并且方法名首字母也是大写时,方法可以被接口是所在的包 之外的代码访问

    • 参数列表、返回值列表:其中的参数变量名可以省略

实现接口的条件

  • 接口 是一个需要实现的方法列表

  • 实现了接口中所有方法,就实现了这个接口

  • 示例:

    • 定义 Sayer 接口:

      1
      2
      3
      type Sayer interface{
      say()
      }
    • 定义结构体

      1
      2
      type dog struct{}
      type cat struct{}
    • 需要对dog和cat分别实现 say方法,即可实现Sayer接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // dog实现了Sayer接口
      func (d dog) say() {
      fmt.Println("汪汪汪")
      }

      // cat实现了Sayer接口
      func (c cat) say() {
      fmt.Println("喵喵喵")
      }

接口类型变量

  • 接口类型变量存储所有实现了该接口的实例

    • 例如,Sayer 类型的变量能存储 dogcat类型的变量
    • ```go
      func main() {
      var x Sayer // 声明一个Sayer类型的变量x
      a := cat{}  // 实例化一个cat
      b := dog{}  // 实例化一个dog
      x = a       // 可以把cat实例直接赋值给x
      x.say()     // 喵喵喵
      x = b       // 可以把dog实例直接赋值给x
      x.say()     // 汪汪汪
      
      }
      1
      2
      3
      4
      5
      6
      7
      * ```go
      // 摘自gin框架routergroup.go
      type IRouter interface{ ... }

      type RouterGroup struct { ... }

      var _ IRouter = &RouterGroup{} // 确保RouterGroup实现了接口IRouter
  • 确保 RouterGroup 一定实现了 IRouter

值接收者 和 指针接收者 实现接口的区别

  • 示例,一个 Mover 接口和 dog 结构体

    • ```go
      type Mover interface {

      move()
      

      }

      type dog struct {}

      1
      2
      3
      4
      5
      6
      7
      8
      9

      ## 值接收者实现接口

      * dog 类型实现接口:

      * ```go
      func (d dog) move() {
      fmt.Println("狗会动")
      }
  • ```go
    func main() {

    var x Mover
    var wangcai = dog{} // 旺财是dog类型
    x = wangcai         // x可以接收dog类型
    var fugui = &dog{}  // 富贵是*dog类型
    x = fugui           // x可以接收*dog类型
    x.move()
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    * 使用值接收者实现接口 后,d**og 结构体和结构体指针类型的变量** 都可以赋值给该接口变量

    * 由于Go中有对指针类型变量求值的语法糖,指针类型内部会自动求值

    ### 指针接收者实现接口

    ```go
    func (d *dog) move() {
    fmt.Println("狗会动")
    }
    func main() {
    var x Mover
    var wangcai = dog{} // 旺财是dog类型
    x = wangcai // x不可以接收dog类型
    var fugui = &dog{} // 富贵是*dog类型
    x = fugui // x可以接收*dog类型
    }
  • 此时实现 Mover 接口的是 *dog类型,所以不能给 x 传入 dog类型的wangcai,此时x只能存储*dog类型的值

面试题

  • 能否通过编译

  • ```go
    type People interface {

    Speak(string) string
    

    }

    type Student struct{}

    func (stu *Student) Speak(think string) (talk string) {

    if think == "sb" {
        talk = "你是个大帅比"
    } else {
        talk = "您好"
    }
    return
    

    }

    func main() {

    var peo People = Student{}
    think := "bitch"
    fmt.Println(peo.Speak(think))
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    * 不能。可能是因为 `var peo People = Student{}`

    * 实现People接口的是 `*Student`类型
    * 将其修改后:`您好`

    # 类型与接口的关系

    ## 一个类型实现多个接口

    * **一个类型**可以**同时实现多个接口**,而接口间彼此独立,不知道对方的实现

    * 例如,狗可以叫,也可以动。定义 `Sayer` 和 `Mover` 接口:

    * ```go
    // Sayer 接口
    type Sayer interface {
    say()
    }

    // Mover 接口
    type Mover interface {
    move()
    }
    • 对 dog 实现 SayerMover 接口

      • ```go
        type dog struct {

        name string
        

        }

        // 实现Sayer接口
        func (d dog) say() {

        fmt.Printf("%s会叫汪汪汪\n", d.name)
        

        }

        // 实现Mover接口
        func (d dog) move() {

        fmt.Printf("%s会动\n", d.name)
        

        }

        func main() {

        var x Sayer
        var y Mover
        
        var a = dog{name: "旺财"}
        x = a
        y = a
        x.say()
        y.move()
        

        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

        ## 多个类型实现统一接口

        * **不同的类型** 可以 **实现统一接口**

        * 定义一个 `Mover`接口,有一个 move 方法

        * ```go
        // Mover 接口
        type Mover interface {
        move()
        }
    • 多个类型实现 Mover的方法 move

      • ```go
        type dog struct {

        name string
        

        }

        type car struct {

        brand string
        

        }

        // dog类型实现Mover接口
        func (d dog) move() {

        fmt.Printf("%s会跑\n", d.name)
        

        }

        // car类型实现Mover接口
        func (c car) move() {

        fmt.Printf("%s速度70迈\n", c.brand)
        

        }

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        * 在代码中,就可以把不同的类型,当成拥有同一特性的物体处理,不需要关注具体是什么,

        * ```go
        func main() {
        var x Mover
        var a = dog{name: "旺财"}
        var b = car{brand: "保时捷"}
        x = a
        x.move()
        x = b
        x.move()
        }
  • 一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现

    • ```go
      // WashingMachine 洗衣机
      type WashingMachine interface {

      wash()
      dry()
      

      }

      // 甩干器
      type dryer struct{}

      // 实现WashingMachine接口的dry()方法
      func (d dryer) dry() {

      fmt.Println("甩一甩")
      

      }

      // 海尔洗衣机
      type haier struct {

      dryer //嵌入甩干器
      

      }

      // 实现WashingMachine接口的wash()方法
      func (h haier) wash() {

      fmt.Println("洗刷刷")
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      # 接口嵌套

      * 接口与接口 间 可以通过 嵌套创造新的接口

      * ```go
      // Sayer 接口
      type Sayer interface {
      say()
      }

      // Mover 接口
      type Mover interface {
      move()
      }

      // 接口嵌套
      type animal interface {
      Sayer
      Mover
      }
  • 嵌套得到的接口 使用与普通接口一致,

    • ```go
      type cat struct {

      name string
      

      }

      func (c cat) say() {

      fmt.Println("喵喵喵")
      

      }

      func (c cat) move() {

      fmt.Println("猫会动")
      

      }

      func main() {

      var x animal
      x = cat{name: "花花"}
      x.move()
      x.say()
      

      }

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      # 空接口

      ## 空接口的定义

      * 没有指定任何方法的接口,**任何类型都实现了空接口**
      * **空接口类型的变量**可以**存储任意类型的变量**

      * ```go
      func main() {
      // 定义一个空接口x
      var x interface{}
      s := "Hello 沙河"
      x = s
      fmt.Printf("type:%T value:%v\n", x, x)
      i := 100
      x = i
      fmt.Printf("type:%T value:%v\n", x, x)
      b := true
      x = b
      fmt.Printf("type:%T value:%v\n", x, x)
      }

空接口的应用

空接口作为函数的参数

  • 使用空接口可以实现接受 任意类型的函数参数

    • ```go
      // 空接口作为函数参数
      func show(a interface{}) {
      fmt.Printf("type:%T value:%v\n", a, a)
      
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      ### 空接口作为 map 的值

      * 使用空接口实现 可以**保存任意值的字典**

      * ```go
      // 空接口作为map值
      var studentInfo = make(map[string]interface{})
      studentInfo["name"] = "沙河娜扎"
      studentInfo["age"] = 18
      studentInfo["married"] = false
      fmt.Println(studentInfo)

类型断言

  • 空接口实现,可以使得map存储任意类型的值,
  • 如何获取存储的具体数据

接口值

  • 一个接口的值 简称为接口值废话

  • 接口值一个具体的类型具体类型的值 组成

    • 称为 接口的 动态类型动态值
  • 示例:

    • ```go
      var w io.Writer
      w = os.Stdout
      w = new(bytes.Buffer)
      w = nil

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
        * ![接口值图解](E:\LearningNotes\Go\14 接口.assets\interface.png)
      * **判断空接口中的值**,使用类型断言,格式

      * `x.(T)`
      * `x` : 表示类型为 `interface{}` 的变量
      * `T `:表示断言 x 可能是的类型
      * 该语法返回两个参数

      * **第一个:**x 转化为 T 类型后的变量
      * **第二个 :**布尔值,若为 `true` 表示断言成功
      * 示例:

      * ```go
      func main() {
      var x interface{}
      x = "Hello 沙河"
      v, ok := x.(string)
      if ok {
      fmt.Println(v)
      } else {
      fmt.Println("类型断言失败")
      }
      }
    • 使用 switch 语句实现:

      • ```go
        func justifyType(x interface{}) {
        switch v := x.(type) {
        case string:
            fmt.Printf("x is a string,value is %v\n", v)
        case int:
            fmt.Printf("x is a int is %v\n", v)
        case bool:
            fmt.Printf("x is a bool is %v\n", v)
        default:
            fmt.Println("unsupport type!")
        }
        
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        * ==空接口 可以存储任意类型值==
        * **注意**:只有当两个以上的**具体类型必须以相同的方式处理**时,才需要定义接口

        # 练习题

        * 使用接口的方式实现一个既可以往终端写日志也可以往文件写日志的简易日志库
        * `os` 和 `io` 包的学习:

        * `os`
        * `io`

        * `io.Writer` :表示一个编写器,从缓冲区读取数据,并将数据写入目标资源

        * 必须实现 `io.Writer`接口的唯一方法 `Write(p []byte)`
        * ```go
        type Writer interface {
        Write(p []byte) (n int, err error) //返回写入到目标资源的字节数,发生错误时的错误
        }