- 原文地址:Part 19: Interfaces - II
- 原文作者:Naveen R
- 譯者:咔嘰咔嘰 轉載請註明出處。
指標接收者的介面 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()
}
複製程式碼
在上面的程式中的第 13 行,Person
結構使用值接收者實現了Describer
介面。
正如我們之前已經學過和討論的方法那樣,帶有值接收者的方法同時接受指標和值接收者。用值或者值的解引用去呼叫值方法是合法的。
p1
是Person
型別的值,它在第 29 行中賦值給d1
。Person
實現了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())
}
複製程式碼
上面程式在第 7 行和第 11 行分別宣告瞭兩個介面SalaryCalculator
和LeaveCalculator
。
在第 15 行中定義的Employee
結構,實現了SalaryCalculator
介面的DisplaySalary
方法和LeaveCalculator
介面的CalculateLeavesLeft
方法。現在,Employee
實現了SalaryCalculator
和LeaveCalculator
介面。
在第 41 行,我們將e
賦值給SalaryCalculator
介面型別的變數。在第 43 行,我們將相同的變數e
分配給LeaveCalculator
介面型別的變數。這就使得Employee
型別的變數e
實現了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())
}
複製程式碼
上面程式的第 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 $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
介面上呼叫方法,程式將會發生panic
,因為nil
介面既沒有具體值也沒有具體型別。
package main
type Describer interface {
Describe()
}
func main() {
var d1 Describer
d1.Describe()
}
複製程式碼
由於上面程式中的d1
是nil
,因此程式將會出現panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527]"