接口-II
Q7nl1s admin

使用指针接收器和值接收器来实现接口

我们在第一部分讨论的所有接口实例都是用值接收器实现的。也可以使用指针接收器来实现接口。在使用指针接收器实现接口时,有一个细微的问题需要注意。让我们通过下面的程序来理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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结构在第13行使用值接收器实现了Describer接口。

正如我们在讨论方法时已经学到的,带有值接收器的方法既接受指针也接受值接收器。对任何是一个值或者其值可以被取消引用的东西调用一个值方法是合法的。

p1是一个Person类型的值,它在第29行被分配给d1Person实现了Describer接口,因此第30行将打印Sam is 25 years old

同样地,d1在第32行被分配给&p2。因此,第33行将打印James is 32 years old。Awesome:)。

Address结构在第22行使用指针接收器实现了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接口是在第22行用Address指针接收器实现的,而我们正试图向其赋值a,它是一个值类型,但没有实现Describer接口。这肯定会让你感到惊讶,因为我们早先学过,带有指针接收器的方法会同时接受指针和值接收器。那么为什么第45行的代码不能工作呢?

原因是,对任何已经是指针的东西或其地址可以被获取的东西调用指针值方法是合法的。存储在接口中的具体数值是不可寻址的,因此编译器不可能自动获取第45行中a的地址。因此这段代码失败了。

第47行是可行的,因为我们正在将a的地址&a分配给d2

该程序的其余部分是不言自明的。这个程序将打印。

1
2
3
Sam is 25 years old  
James is 32 years old
State Washington Country USA

实现多个接口

一个类型可以实现一个以上的接口。让我们看看在下面的程序中是如何做到这一点的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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())
}

上面的程序有两个接口SalaryCalculatorLeaveCalculator,分别在第7行和第11行声明。

第15行定义的Employee结构为第7行SalaryCalculator接口的DisplaySalary方法提供了实现。第15行定义的Employee结构为第24行的SalaryCalculator接口的DisplaySalary方法和第28行的LeaveCalculator接口的CalculateLeavesLeft方法提供实现。现在Employee同时实现了SalaryCalculatorLeaveCalculator接口。

在第41行,我们将e分配给SalaryCalculator接口的一个变量,在第43行,我们将同一个变量e赋值给一个LeaveCalculator接口的一个变量。这是有可能的,因为Employee类型的e同时实现了SalaryCalculatorLeaveCalculator接口。

这个程序的输出。

1
2
Naveen Ramanathan has salary $5200  
Leaves left = 25

接口嵌入

尽管go不提供继承性,但我们可以通过嵌入其他接口来创建一个新的接口。

让我们看看这是如何做到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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())
}

上面程序第15行的EmployeeOperations接口是通过嵌入SalaryCalculatorLeaveCalculator接口创建的。

如果任何类型提供了SalaryCalculatorLeaveCalculator接口中的方法定义,就可以说它实现了EmployeeOperations接口。

Employee结构实现了EmployeeOperations接口,因为它在第29行和第33行分别为DisplaySalaryCalculateLeavesLeft方法提供了定义。

在第46行,Employee类型的e被分配给EmployeeOperations类型的empop。在接下来的两行中,对empOp调用了DisplaySalary()CalculateLeavesLeft()方法。

这个程序将输出

1
2
Naveen Ramanathan has salary $5200  
Leaves left = 25

接口的零值

一个接口的零值是nil。一个nil接口的基础值和具体类型都是nil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
}
}

上面的程序中d1nil,这个程序将输出

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

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

1
2
3
4
5
6
7
8
9
10
package main

type Describer interface {
Describe()
}

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

由于上面的程序中d1nil,这个程序会出现运行时错误

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]”

接口就是这样。祝你有个愉快的一天。

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View