Golang:接口

作者: 与蟒唯舞 | 来源:发表于2018-01-15 18:24 被阅读43次
    什么是接口

    在 Golang 中,一个接口是一组方法签名。当一个类型定义了接口里所有定义的方法时,就说这个类型实现了这个接口。接口指定类型应该具有的方法,类型决定如何实现这些方法。

    声明和实现一个接口
    package main
    
    import (  
        "fmt"
    )
    
    //interface definition
    type VowelsFinder interface {  
        FindVowels() []rune
    }
    
    type MyString string
    
    //MyString implements VowelsFinder
    func (ms MyString) FindVowels() []rune {  
        var vowels []rune
        for _, rune := range ms {
            if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
                vowels = append(vowels, rune)
            }
        }
        return vowels
    }
    
    func main() {  
        name := MyString("Sam Anderson")
        var v VowelsFinder
        v = name // possible since MyString implements VowelsFinder
        fmt.Printf("Vowels are %c", v.FindVowels())
    
    }
    

    在上面的程序中,创建了一个接口类型名为 VowelsFinder,它有一个方法 FindVowels() []rune

    通过添加一个方法 FindVowels() []rune 给接受者类型 MyString。现在可以说 MyString 实现了 VowelsFinder 接口。在 Golang 中,如果一个类型包含了一个接口声明的所有方法,那么这个类型就隐式地实现了这个接口。

    MyString 类型的变量 name 赋值给 VowelsFinder 类型的变量 v。这是合法的,因为 MyString 实现了 VowelsFinderv.FindVowels()MyString 上调用 FindVowels 方法打印字符串 Sam Anderson 中所有的元音。

    空接口

    一个没有声明任何方法的接口称为空接口。空接口表示为 interface{}。因为空接口没有方法,因此所有类型都实现了空接口。

    package main
    
    import (  
        "fmt"
    )
    
    func describe(i interface{}) {  
        fmt.Printf("Type = %T, value = %v\n", i, i)
    }
    
    func main() {  
        s := "Hello World"
        describe(s)
        i := 55
        describe(i)
        strt := struct {
            name string
        }{
            name: "Naveen R",
        }
        describe(strt)
    }
    

    上面的程序,describe(i interface{}) 函数接受一个空接口作为参数,因此任何值都可以传递给它。

    接口内部表示

    一个接口可以被认为是由一个元组内部表示的 (type, value)type 是接口的具体类型,value 是具体类型的值。

    package main
    
    import (  
        "fmt"
    )
    
    type Test interface {  
        Tester()
    }
    
    type MyFloat float64
    
    func (m MyFloat) Tester() {  
        fmt.Println(m)
    }
    
    func describe(t Test) {  
        fmt.Printf("Interface type %T value %v\n", t, t)
    }
    
    func main() {  
        var t Test
        f := MyFloat(89.7)
        t = f
        describe(t)
        t.Tester()
    }
    

    Test 接口提供了一个方法 Tester()MyFloat 类型实现了这个接口。将 MyFloat 类型的变量 f 赋值给 Test 类型的变量 t 。现在 t 的具体类型是 MyFloat 而它的值是 89.7。

    接口的零值

    接口的零值是 nil。一个 nil 接口的底层类型和值都是 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)
        }
    }
    

    如果试图在一个 nil 接口上调用方法,程序将会触发 panic,因为 nil 接口既没有底层的值,也没有具体的类型。

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

    上面的程序,因为 d1nil。程序将在运行时触发 panicruntime error: invalid memory address or nil pointer dereference

    实现多个接口

    一个类型可以实现多个接口。

    package main
    
    import (  
        "fmt"
    )
    
    type SalaryCalculator interface {  
        DisplaySalary()
    }
    
    type LeaveCalculator interface {  
        CalculateLeavesLeft() int
    }
    
    type Employee struct {  
        firstName string
        lastName string
        basicPay int
        pf int
        totalLeaves int
        leavesTaken int
    }
    
    func (e Employee) DisplaySalary() {  
        fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
    }
    
    func (e Employee) CalculateLeavesLeft() int {  
        return e.totalLeaves - e.leavesTaken
    }
    
    func main() {  
        e := Employee {
            firstName: "Naveen",
            lastName: "Ramanathan",
            basicPay: 5000,
            pf: 200,
            totalLeaves: 30,
            leavesTaken: 5,
        }
        var s SalaryCalculator = e
        s.DisplaySalary()
        var l LeaveCalculator = e
        fmt.Println("\nLeaves left =", l.CalculateLeavesLeft())
    }
    
    嵌入接口

    尽管 Golang 不提供继承,但是可以通过嵌入其他接口来创建新的接口。

    package main
    
    import (  
        "fmt"
    )
    
    type SalaryCalculator interface {  
        DisplaySalary()
    }
    
    type LeaveCalculator interface {  
        CalculateLeavesLeft() int
    }
    
    type EmployeeOperations interface {  
        SalaryCalculator
        LeaveCalculator
    }
    
    type Employee struct {  
        firstName string
        lastName string
        basicPay int
        pf int
        totalLeaves int
        leavesTaken int
    }
    
    func (e Employee) DisplaySalary() {  
        fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
    }
    
    func (e Employee) CalculateLeavesLeft() int {  
        return e.totalLeaves - e.leavesTaken
    }
    
    func main() {  
        e := Employee {
            firstName: "Naveen",
            lastName: "Ramanathan",
            basicPay: 5000,
            pf: 200,
            totalLeaves: 30,
            leavesTaken: 5,
        }
        var empOp EmployeeOperations = e
        empOp.DisplaySalary()
        fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft())
    }
    
    类型断言

    类型断言用来提取接口的实际类型的值。

    语法:i.(T),用来获取接口 i 的实际类型 T 的值。

    package main
    
    import (  
        "fmt"
    )
    
    func assert(i interface{}) {  
        s := i.(int) //get the underlying int value from i
        fmt.Println(s)
    }
    func main() {  
        var s interface{} = 56
        assert(s)
    }
    

    变量 s 的实际类型是 int。使用 i.(int) 来获取 iint 值。

    如果实际类型不是 int,那么上面的程序会发生什么?

    package main
    
    import (  
        "fmt"
    )
    
    func assert(i interface{}) {  
        s := i.(int) 
        fmt.Println(s)
    }
    func main() {  
        var s interface{} = "Steven Paul"
        assert(s)
    }
    

    在上面的程序中,将实际类型为 string 的变量 s 传递给 assert 函数,assert 函数尝试从其中提取出一个 int 值。该程序会触发 panicinterface conversion: interface {} is string, not int

    为了解决以上问题,可以使用下面的语法:

    v, ok := i.(T)  
    

    如果接口 i 的具体类型是 T,则 v 将具有 i 的实际值,ok 为 true。

    如果接口 i 的具体类型不是 T,则 ok 为 false, vT 的零值,但程序不会触发 panic

    package main
    
    import (  
        "fmt"
    )
    
    func assert(i interface{}) {  
        v, ok := i.(int)
        fmt.Println(v, ok)
    }
    func main() {  
        var s interface{} = 56
        assert(s)
        var i interface{} = "Steven Paul"
        assert(i)
    }
    

    当把字符串 Steven Paul 传递给 assert 函数,ok 将是 false,因为 i 的实际类型不是 intv 的值将是 0(int 的零值)。

    Type Switch

    类型分支(type switch)用来将一个接口的具体类型与多个 case 语句指定的类型进行比较。在类型断言 i.(T) 中,将类型 T 替换为关键字 type 就变成了 type switch。

    package main
    
    import (  
        "fmt"
    )
    
    func findType(i interface{}) {  
        switch i.(type) {
        case string:
            fmt.Printf("I am a string and my value is %s\n", i.(string))
        case int:
            fmt.Printf("I am an int and my value is %d\n", i.(int))
        default:
            fmt.Printf("Unknown type\n")
        }
    }
    func main() {  
        findType("Naveen")
        findType(77)
        findType(89.98)
    }
    

    也可以将类型与接口进行比较。如果我们有一个类型,并且如果这个类型实现了一个接口,就可以将这个类型与它实现的接口进行比较。

    package main
    
    import "fmt"
    
    type Describer interface {  
        Describe()
    }
    type Person struct {  
        name string
        age  int
    }
    
    func (p Person) Describe() {  
        fmt.Printf("%s is %d years old", p.name, p.age)
    }
    
    func findType(i interface{}) {  
        switch v := i.(type) {
        case Describer:
            v.Describe()
        default:
            fmt.Printf("unknown type\n")
        }
    }
    
    func main() {  
        findType("Naveen")
        p := Person{
            name: "Naveen R",
            age:  25,
        }
        findType(p)
    }
    

    相关文章

      网友评论

        本文标题:Golang:接口

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