美文网首页go学习
Go语言interface的介绍

Go语言interface的介绍

作者: witchiman | 来源:发表于2017-06-05 09:48 被阅读210次

    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

    相关文章

      网友评论

        本文标题:Go语言interface的介绍

        本文链接:https://www.haomeiwen.com/subject/wxhifxtx.html