Go教程第十六篇: 接口-2

2020-05-05  本文已影响0人  大风过岗

Go教程第十六篇: 接口-2

本文是《Go系列教程》的第十六篇文章,本章我们将讲解接口的第二部分。

使用指针接收者和使用值接收者实现接口

我们在接口第一部分中使用到的例子都是使用值接收者实现的。另外,除了使用值接收者外,我们还可以使用指针接收者。但是在使用指针接收者实现接口的时候,有个微妙的地方需要指出来。我们使用下面这个程序来说明一下。

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

}

在上面的程序中,结构体Person是使用值接收者来实现Describer接口。正如我们之前在方法中讲过的一样,带有值接收者的方法可以同时接收指针和值。我们在调用值方法时,不仅可以传入值,还可以传入一个引用。

p1是一个Person类型的值,它被赋值给d1。Person实现了Describer接口,所以才会打印出“Sam is 25 years old”。类似地,把&p2赋值给d1,因而,打印的结果是:"Jame 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).这是因为在22行我们是使用一个Address类型的指针接收者来实现的Describer接口,而a是一个值类型,并且它没有实现Describer接口。你肯定感觉到很吃惊,因为我们之前在学习方法的时候,就知道形参为指针接收者的方法,可以同时接收指针和值。
但是,为什么第45行的代码就不能正常运行了呢。

The reason is that it is legal to call a pointer-valued method on anything that is already a pointer or whose address can be taken.
The concrete value stored in an interface is not addressable and hence it is not possible for the compiler to automatically take the address of a in line no. 45 and hence this code fails。

其余的部分都比较易于理解,程序将输出如下:

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

在上面的程序中有俩个接口:SalaryCalculator和LeaveCalculator。在程序的第15行,我们定义了一个结构体Employee,它实现了SalaryCalculator接口DisplaySalary方法以及LeaveCalculator接口的CalculateLeavesLeft方法。现在Employee就同时实现了SalaryCalculator和LeaveCalculator接口。

在程序的第41行,我们把e分配给SalaryCalculator接口类型的变量,在43行,我们把变量e再次分配给LeaveCalculator类型的变量。这样做是可以的,因为e的数据类型是Employee,而Employee同时实现了SalaryCalculator和LeaveCalculator接口。
程序的输出如下:

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

在上面的程序中,我们创建了一个EmployeeOperations接口,在它的内部我们内嵌了SalaryCalculator 和LeaveCalculator 接口。

任何一个类,如果它实现了SalaryCalculator 和LeaveCalculator接口中的方法,我们都可以说,它实现了EmployeeOperations接口。Employee结构体就实现了EmployeeOperations接口,因为它分别提供了DisplaySalary 和CalculateLeavesLeft方法的定义。

在第46行,当我们把Employee类型的变量e分配给了EmployeeOperations类型的变量empOp。紧接着,我们又调用了empOp的DisplaySalary和CalculateLeavesLeft方法
程序的输出如下:

Naveen Ramanathan has salary $5200  
Leaves left = 25  

接口的零值

接口的零值是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)
    }
}

上面的程序中的d1是nil,并且程序的输出为:

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

如果我们试图调用nil接口的方法,程序就会出错。因为nil接口既没有潜在值也没有具体的类型。

package main

type Describer interface {  
    Describe()
}

func main() {  
    var d1 Describer
    d1.Describe()
}

因为d1是nil,所以程序会抛出运行时异常:runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]"

感谢您的阅读,请留下您珍贵的反馈和评论。Have a good Day!

备注
本文系翻译之作原文博客地址

上一篇下一篇

猜你喜欢

热点阅读