zoukankan      html  css  js  c++  java
  • Go语言基础之接口定义

    Go语言基础之接口定义

    接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

    一、接口类型

    在Go语言中接口(interface)是一种类型,一种抽象的类型,引用类型。

    interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。

    为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型。

    二、 为什么要使用接口

    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())
    }
    

    上面的代码中定义了猫和狗,然后它们都会叫,你会发现main函数中明显有重复的代码,如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢?

    像类似的例子在我们编程过程中会经常遇到:

    比如一个网上商城可能使用支付宝、微信、银联等方式去在线支付,我们能不能把它们当成“支付方式”来处理呢?

    比如三角形,四边形,圆形都能计算周长和面积,我们能不能把它们当成“图形”来处理呢?

    比如销售、行政、程序员都能计算月薪,我们能不能把他们当成“员工”来处理呢?

    Go语言中为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一种抽象的类型。当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。

    三、接口的定义

    Go语言提倡面向接口编程。

    每个接口由数个方法组成,接口的定义格式如下:

    type 接口类型名 interface{
        方法名1( 参数列表1 ) 返回值列表1
        方法名2( 参数列表2 ) 返回值列表2
        …
    }
    

    其中:

    • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义
    • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
    • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

    举个例子:

    type writer interface{
        Write([]byte) error
    }
    

    当你看到这个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的Write方法来做一些事情。

    四、实现接口的条件

    一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表

    我们来定义一个Sayer接口:

    // Sayer 接口
    type Sayer interface {
        say()
    }
    

    定义dogcat两个结构体:

    type dog struct {}
    
    type cat struct {}
    

    因为Sayer接口里只有一个say方法,所以我们只需要给dogcat分别实现say方法就可以实现Sayer接口了。

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

    接口的实现就是这么简单,只要实现了接口中的所有方法,就实现了这个接口。

    五、接口类型变量

    那实现了接口有什么用呢?

    接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dogcat类型的变量。

    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、如果下述代码可以赋值成功,则证明结构体实现了接口,因为此处我们只想验证,所以变量名无关紧要,推荐用_代表
    var _ 接口类型 = &结构体{}  
    
    // 2、例如:
    var _ DuckInterface = &PDuck{}
    

    image-20211101221246246

    // 摘自gin框架routergroup.go 体味此处_的妙用
    type IRouter interface{ ... }
    
    type RouterGroup struct { ... }
    
    var _ IRouter = &RouterGroup{}  // 确保RouterGroup实现了接口IRouter
    

    任意的类型都可以赋值给空接口类型

    type Empty interface {
    }
    //3 空接口(没有方法,所有类型其实都实现了空接口,于是:任意的类型都可以赋值给空接口类型)
    var a Empty=10
    a="ssddd"
    a = TDuck{}
    fmt.Println(a)
    

    六、接口零值

    接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil

    package main
    
    import "fmt"
    
    type Describer interface {  
        Describe()
    }
    
    func main() {  
        var d1 Describer
        if d1 == nil {
            fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
        }
    }
    

    上面程序里的 d1 等于 nil,程序会输出:

    d1 is nil and has type <nil> value <nil>
    

    对于值为 nil 的接口,由于没有底层值和具体类型,当我们试图调用它的方法时,程序会产生 panic 异常。

    package main
    
    type Describer interface {
        Describe()
    }
    
    func main() {  
        var d1 Describer
        d1.Describe()
    }
    

    在上述程序中,d1 等于 nil,程序产生运行时错误 panicpanic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]

    七、总结

    1. 接口定义

      • 接口名单词后面加er,表示是接口
      • 接口名和方法名大写表示可以被包之外代码可以访问
      type 接口类型名 interface{
          方法名1( 参数列表1 ) 返回值列表1
          方法名2( 参数列表2 ) 返回值列表2
          …
      }
      
    2. 如果一个结构体实现接口中所有的方法,那么就实现了这个接口

    3. 接口是一种类型,引用类型,默认值:nil

    4. 空接口可以接收任何类型

    5. 判断结构体是否实现了接口:var _ 接口类型 = &结构体{}

  • 相关阅读:
    201805140815_《缓存操作函数封装》
    201802071223_《更换两个二进制》
    201801301359——《注意Javascript这种形式》
    201708310807_《算法-Javascript实现最大公约数》
    重拾java openjdk1.8 语法小试
    代码轮子之很简单但是挺管用的基于C# Task的模拟并发的代码
    docker1.12 安装pxc(Percona XtraDB Cluster )测试
    .net orm比较之dapper和Entity Framework6的简单测试比较
    StackExchange.Redis使用和封装小试
    docker1.12 安装redis3官方集群 攻略
  • 原文地址:https://www.cnblogs.com/randysun/p/15496477.html
Copyright © 2011-2022 走看看