Go语言interface的介绍

Interface 是Go语言里很优秀的一个设计,它本身是仅仅是一个结构体,但是通过interface我们可以实现面向对象的很多特性,比如多态、动态联编等。interface 在Go 语言里使用起来的感觉,相比于其它语言也要优雅得多。

底层实现

Interface 的底层只是一个简单的由c语言实现的数据结构:

struct Eface
{
    Type*    type;
    void*    data;
};

type 表示它的类型,data 里存储了它的具体信息。对于Go语言,任何数据类型都是可以由interface{}来表示的。当我们使用时可以通过反射把里面的信息取出来。

方法实现

我们先定义一个简单的接口,类似于Java 中的那样。

type Animal interface {
    Run()
    Roar()
}

定义一个结构体 Dog ,直接定义Run 和 Roar 方法,即可实现接口。

type Dog struct {
    Name string
    Size string
}

func (d *Dog)Run()  {
    fmt.Printf("Dog %s run!\n", d.Name)
}

func (d *Dog)Roar()  {
    fmt.Printf("A %s dog roar!\n", d.Size)
}

声明一个Dog 类型并初始化。

func main () {
    d := Dog{
            Name: "Jim",
            Size: "big",
        }
    d.Run()
    d.Roar()
}
// output: 
//      Dog Jim run!
//      A big dog roar!

这样实现一个接口的方法很简单,只需要在定义某一类型的时候,定义一个同名的方法即可。

多态

我们可以直接声明一个该接口的变量,再让这个变量指向实现其方法类型的实例的地址,这个变量就可以调用此实例的方法。

func main() {
    ...
    var a Animal
    a = &d
    a.Run()
    a.Roar()
}
// output:
//      Dog Jim run!
//      A big dog roar!

同样定义另外一个实现该接口的类型。

type Cat struct {
    Name string
    Color string
}

func (c *Cat)Run()  {
    fmt.Printf("Cat %s run!\n", c.Name)
}

func (c *Cat)Roar()  {
    fmt.Printf("A %s cat roar!\n", c.Color)
}

再让上面声明的接口变量指向这个类型的实例的地址,实际上调用方法的对象则变成后者。

func main() {
    ...
    c := Cat{
        Name: "Kitty",
        Color: "yello",
    }
    a = &c
    c.Roar()
    c.Run()
}
//output:
//      A yello cat roar!
//      Cat Kitty run!

Go语言里面向对象的特性通过interface 很轻易地体现了出来。本质上上述声明的Animal 变量a 是一个指针,它底层的内存结构包含两个字段:

  • receiver,a可以指向任意变量,只需要这个变量的类型实现了该接口,此时receiver 存储该变量的地址,实际上此地址指向的其实是该变量的一个副本,因为Go 会在堆上申请一段空间用以存储变量的副本。如果该变量不大于一个字,则该变量直接存储在receiver 中。
  • method table ptr,类似于C++ 里的虚函数表,当a 指向某实例变量后,该字段存储其方法的入口地址。

类型转换

一个空的 interface ,不包含任何方法,因此它能够存储任意类型的值。

func main() {
    var i interface{}
    a := 1
    i = a
    fmt.Println(i)
    b := true
    i = b
    fmt.Println(i)
}
//output:
//      1
//      true

我们可以借此实现一个函数,它的参数可以接收任意类型的值。这时,interface 的用法就比较像Java里的Object 对象。同样,我们使用这个参数前需要类型转换,在Java里可以通过运算符instanceof 来判断一个对象的实例是否为某一类型,并可以通过显示类型转换来使用这个实例。 在Go语言里,我们通过断言一样可以做到这些。先看看断言的用法:

func testFunc(animal interface{})  {
    if c, ok := animal.(Cat); ok {
        fmt.Println(c)
    } else {
            fmt.println("It's not a cat!")
    }
}

格式为:变量名.(类型名)。返回两个值,第一个值为转换类型后的实例,第二个值为布尔变量,如果传入的参数为Cat 类型就返回 true, 否则返回 false 。 调用testFunc 时,在函数里判断其传入的参数是否为Cat 类型,如果是就打印出它的值。

也可以直接使用断言返回的第一个值。用法如c := animal.(Cat),直接忽略判断的结果。下面我们为testFunc 加入更多类型的判断,这时,通过结合switch的使用,使代码可读性更加友好。

func testFunc(animal interface{})  {
    switch animal.(type) {
    case Dog:
        fmt.Println(animal)
    case Cat:
        fmt.Println(animal)
    default:
        fmt.Println("It's nothing!")
    }
}

个人博客 http://witchiman.github.io

    原文作者:witchiman
    原文地址: https://www.jianshu.com/p/f57b82195289
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞