[譯] part 19: golang 介面 2

咔嘰咔嘰發表於2019-04-09

指標接收者的介面 VS 值接收者的介面

我們在上一篇文章中討論的所有示例介面都是使用值接收者實現的。也可以使用指標接收者實現介面。在使用指標接收者實現介面時需要注意一些細微之處。讓我們使用以下程式瞭解一下。

package main

import "fmt"

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe() { //implemented using value receiver  
    fmt.Printf("%s is %d years old\n", p.name, p.age)
}

type Address struct {  
    state   string
    country string
}

func (a *Address) Describe() { //implemented using pointer receiver  
    fmt.Printf("State %s Country %s", a.state, a.country)
}

func main() {  
    var d1 Describer
    p1 := Person{"Sam", 25}
    d1 = p1
    d1.Describe()
    p2 := Person{"James", 32}
    d1 = &p2
    d1.Describe()

    var d2 Describer
    a := Address{"Washington", "USA"}

    /* compilation error if the following line is
       uncommented
       cannot use a (type Address) as type Describer
       in assignment: Address does not implement
       Describer (Describe method has pointer
       receiver)
    */
    //d2 = a

    d2 = &a //This works since Describer interface
    //is implemented by Address pointer in line 22
    d2.Describe()

}
複製程式碼

Run in playground

在上面的程式中的第 13 行,Person結構使用值接收者實現了Describer介面。

正如我們之前已經學過和討論的方法那樣,帶有值接收者的方法同時接受指標和值接收者。用值或者值的解引用去呼叫值方法是合法的。

p1Person型別的值,它在第 29 行中賦值給d1Person實現了d1介面,因此 30 行,將列印Sam is 25 years old.

類似地,在第 32 行中將&p2賦值給d1。 因此第 33 行將列印James is 32 years old.,太棒了:)。

在第 22 行,Address結構使用指標接收者實現Describer介面。 如果上面程式的第 45 行沒有被取消註釋,我們將看到編譯錯誤main.go:42: cannot use a (type Address) as type Describer in assignment: Address does not implement Describer (Describe method has pointer receiver)。這是因為,Describer介面是使用第地址指標接收者實現的,我們嘗試分配一個值型別a,但它沒有實現Describer介面。這肯定會讓你感到驚訝,因為我們之前已經知道帶有指標接收者的方法將同時接受指標和值接收者。那麼第 45 行的程式碼為什麼不行呢?

原因是在任何已經是指標或可以定址的任何型別上呼叫指標值方法是合法的。而儲存在介面中的具體值是不可定址的,因此編譯器不可能自動獲取第 45 行a的地址,因此這段程式碼失敗了。

第 47 行是正確的,因為我們將a的地址&a賦值給了d2

該程式的其餘部分是通俗易懂的。該程式將列印,

Sam is 25 years old  
James is 32 years old  
State Washington Country USA  
複製程式碼

實現多個介面

一個型別可以實現多個介面。讓我們看看如何在以下程式中完成此操作。

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())
}
複製程式碼

Run in playground

上面程式在第 7 行和第 11 行分別宣告瞭兩個介面SalaryCalculatorLeaveCalculator

在第 15 行中定義的Employee結構,實現了SalaryCalculator介面的DisplaySalary方法和LeaveCalculator介面的CalculateLeavesLeft方法。現在,Employee實現了SalaryCalculatorLeaveCalculator介面。

在第 41 行,我們將e賦值給SalaryCalculator介面型別的變數。在第 43 行,我們將相同的變數e分配給LeaveCalculator介面型別的變數。這就使得Employee型別的變數e實現了SalaryCalculatorLeaveCalculator介面。

程式輸出,

Naveen Ramanathan has salary $5200  
Leaves left = 25  
複製程式碼

介面的嵌入

儘管 go 不提供繼承,但可以通過嵌入其他介面來建立新介面。

我們來看看是怎麼完成的。

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())
}
複製程式碼

Run in playground

上面程式的第 15 行中的EmployeeOperations介面是通過嵌入SalaryCalculatorLeaveCalculator介面建立的。

如果一個型別提供了SalaryCalculatorLeaveCalculator介面中存在的方法的方法定義,就可以說實現了EmployeeOperations介面。

Employee結構實現了EmployeeOperations介面,因為它分別在第 29 行和第 33 行中的DisplaySalaryCalculateLeavesLeft方法提供了定義。

在第 46 行,型別為Employeee被賦值給EmployeeOperations型別的empOp。在接下來的兩行中,在empOp上呼叫DisplaySalary()CalculateLeavesLeft()方法。

程式輸出,

Naveen Ramanathan has salary $5200  
Leaves left = 25  
複製程式碼

介面的零值

介面的零值是nilnil介面的值和型別都為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)
    }
}
複製程式碼

Run in playground

上述程式中的d1nil,此程式將輸出

d1 is nil and has type <nil> value <nil> 
複製程式碼

如果我們嘗試在nil介面上呼叫方法,程式將會發生panic,因為nil介面既沒有具體值也沒有具體型別。

package main

type Describer interface {  
    Describe()
}

func main() {  
    var d1 Describer
    d1.Describe()
}
複製程式碼

Run in playground

由於上面程式中的d1nil,因此程式將會出現panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]"

相關文章