使用指针接收器和值接收器实现接口
我们在第 1 部分中讨论的所有示例接口都是使用值接收器实现的。也可以使用指针接收器实现接口。在使用指针接收器实现接口时需要注意一些细微之处。
package mainimport "fmt"type Describer interface {Describe()}type Person struct {name stringage int}func (p Person) Describe() { //implemented using value receiverfmt.Printf("%s is %d years old\n", p.name, p.age)}type Address struct {state stringcountry string}func (a *Address) Describe() { //implemented using pointer receiverfmt.Printf("State %s Country %s", a.state, a.country)}func main() {var d1 Describerp1 := Person{"Sam", 25}d1 = p1d1.Describe()p2 := Person{"James", 32}d1 = &p2d1.Describe()var d2 Describera := Address{"Washington", "USA"}/* compilation error if the following line isuncommentedcannot use a (type Address) as type Describerin assignment: Address does not implementDescriber (Describe method has pointerreceiver)*///d2 = ad2 = &a //This works since Describer interface//is implemented by Address pointer in line 22d2.Describe()}
在上面的程序中,PersonPerson 结构使用第 13 行中的值接收器实现 Describer 接口。
正如我们在讨论方法时已经学到的那样,带有值接收器的方法同时接受指针和值接收器。对任何值或者可以取消引用的值,调用值方法都是合法的。
_p1 是 Person 类型的值,它在第 29 行中分配给 d1。Person 实现了 d1 接口,因此第30 行输出 Sam is 25 years old。
类似地,在 32 行将 d1 分配给 &p2。第 33 行将输出 James is 32 years old。太棒了:)。
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 行中的地址指针接收器实现的,我们正在尝试分配一个值类型 a,但它没有实现 Describer 接口。这肯定会让你感到惊讶,因为我们之前已经知道带有指针接收器的方法将同时接受指针和值接收器。那么为什么第 45 行代码不能工作。
原因是在任何对已经是指针或可以获取其地址的任何对象调用指针值方法都是合法的。存储在接口中的具体值不可寻址,因此编译器无法自动获取 a 的地址。 因此第 45 行这段代码不能工作。
**
第 47 行代码工作因为我们把 a 的地址 &a 分配给 d2。
程序输出
Sam is 25 years oldJames is 32 years oldState Washington Country USA
实现多个接口
一个类型可以实现多个接口。让我们看看如何在以下程序中完成此操作。
package mainimport ("fmt")type SalaryCalculator interface {DisplaySalary()}type LeaveCalculator interface {CalculateLeavesLeft() int}type Employee struct {firstName stringlastName stringbasicPay intpf inttotalLeaves intleavesTaken 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 = es.DisplaySalary()var l LeaveCalculator = efmt.Println("\nLeaves left =", l.CalculateLeavesLeft())}
上面的程序第 7 和 11 行声明了两个接口 SalaryCalculator 和 LeaveCalculator。
第 15 行 Employee 结构定义了在第 24 行 SalaryCalculator 接口的 DisplaySalary 方法和第 28 行 LeaveCalculator 接口的 CalculateLeavesLeft 方法的实现。 现在,Employee 实现了 SalaryCalculator 和 LeaveCalculator 接口。
第 41 行我们将 e 分配给 SalaryCalculator 类型的变量,在第 43 行并将相同的变量 e 分配给 LeaveCalculator 类型的变量。 这是允许的,因为 Employee 类型 e 实现了SalaryCalculator 和 LeaveCalculator 接口。
该程序输出,
Naveen Ramanathan has salary $5200Leaves left = 25
嵌入接口
尽管 go 不提供继承,但可以通过嵌入其他接口来创建新接口。
让我们看看这是如何完成的。
package mainimport ("fmt")type SalaryCalculator interface {DisplaySalary()}type LeaveCalculator interface {CalculateLeavesLeft() int}type EmployeeOperations interface {SalaryCalculatorLeaveCalculator}type Employee struct {firstName stringlastName stringbasicPay intpf inttotalLeaves intleavesTaken 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 = eempOp.DisplaySalary()fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft())}
上面程序的第 15 行中的 EmployeeOperations 接口是通过嵌入 SalaryCalculator 和LeaveCalculator 接口创建的。
如果它为 SalaryCalculator 和 LeaveCalculator 接口中提供的方法提供方法定义,则称任何类型都实现 EmployeeOperations 接口。
Employee 结构实现了 EmployeeOperations 接口,因为它分别为第 29 行和第 33 行中的 DisplaySalary 和 CalculateLeavesLeft 方法提供了定义。
在第 46 行,类型为 Employee 的 e 被分配给 EmployeeOperations 类型的 empOp 。在接下来的两行中,在 empOp 上调用 DisplaySalary() 和 CalculateLeavesLeft()方法。
该程序将输出
Naveen Ramanathan has salary $5200Leaves left = 25
接口零值
接口的零值为零。 nil 接口既有底层值,也有具体类型为 nil。
package mainimport "fmt"type Describer interface {Describe()}func main() {var d1 Describerif 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 接口上调用方法,程序将会发生panic,因为 nil 接口既没有底层值也没有具体类型。
package maintype Describer interface {Describe()}func main() {var d1 Describerd1.Describe()}
由于上面程序中的 d1 是 nil,因此程序会因运行时报错 panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]”
