一、概念
1.接口定義
Go 語言的接口類型非常特別,它的作用和 Java 語言的接口一樣,但是在形式上有很大的差別。Java 語言需要在類的定義上顯式實(shí)現(xiàn)了某些接口,才可以說這個(gè)類具備了接口定義的能力。但是 Go 語言的接口是隱式的,只要結(jié)構(gòu)體上定義的方法在形式上(名稱、參數(shù)和返回值)和接口定義的一樣,那么這個(gè)結(jié)構(gòu)體就自動實(shí)現(xiàn)了這個(gè)接口,我們就可以使用這個(gè)接口變量來指向這個(gè)結(jié)構(gòu)體對象。下面我們看個(gè)例子
package main
import "fmt"
// 可以聞
type Smellable interface {
smell()
}
// 可以吃
type Eatable interface {
eat()
}
// 蘋果既可能聞又能吃
type Apple struct {}
func (a Apple) smell() {
fmt.Println("apple can smell")
}
func (a Apple) eat() {
fmt.Println("apple can eat")
}
// 花只可以聞
type Flower struct {}
func (f Flower) smell() {
fmt.Println("flower can smell")
}
func main() {
var s1 Smellable
var s2 Eatable
var apple = Apple{}
var flower = Flower{}
s1 = apple
s1.smell()
s1 = flower
s1.smell()
s2 = apple
s2.eat()
}
--------------------
apple can smell
flower can smell
apple can eat
上面的代碼定義了兩種接口,Apple 結(jié)構(gòu)體同時(shí)實(shí)現(xiàn)了這兩個(gè)接口,而 Flower 結(jié)構(gòu)體只實(shí)現(xiàn)了 Smellable 接口。我們并沒有使用類似于 Java 語言的 implements 關(guān)鍵字,結(jié)構(gòu)體和接口就自動產(chǎn)生了關(guān)聯(lián)。
2.空接口
如果一個(gè)接口里面沒有定義任何方法,那么它就是空接口,任意結(jié)構(gòu)體都隱式地實(shí)現(xiàn)了空接口。
Go 語言為了避免用戶重復(fù)定義很多空接口,它自己內(nèi)置了一個(gè),這個(gè)空接口的名字特別奇怪,叫 interface{} ,初學(xué)者會非常不習(xí)慣。之所以這個(gè)類型名帶上了大括號,那是在告訴用戶括號里什么也沒有。我始終認(rèn)為這種名字很古怪,它讓代碼看起來有點(diǎn)丑陋。
空接口里面沒有方法,所以它也不具有任何能力,其作用相當(dāng)于 Java 的 Object 類型,可以容納任意對象,它是一個(gè)萬能容器。比如一個(gè)字典的 key 是字符串,但是希望 value 可以容納任意類型的對象,類似于 Java 語言的 Map 類型,這時(shí)候就可以使用空接口類型 interface{}。
package main
import "fmt"
func main() {
// 連續(xù)兩個(gè)大括號,是不是看起來很別扭
var user = map[string]interface{}{
"age": 30,
"address": "Beijing Tongzhou",
"married": true,
}
fmt.Println(user)
// 類型轉(zhuǎn)換語法來了
var age = user["age"].(int)
var address = user["address"].(string)
var married = user["married"].(bool)
fmt.Println(age, address, married)
}
-------------
map[age:30 address:Beijing Tongzhou married:true]
30 Beijing Tongzhou true
代碼中 user 字典變量的類型是 map[string]interface{},從這個(gè)字典中直接讀取得到的 value 類型是 interface{},需要通過類型轉(zhuǎn)換才能得到期望的變量。
3.用接口來模擬多態(tài)
package main
import "fmt"
type Fruitable interface {
eat()
}
type Fruit struct {
Name string // 屬性變量
Fruitable // 匿名內(nèi)嵌接口變量
}
func (f Fruit) want() {
fmt.Printf("I like ")
f.eat() // 外結(jié)構(gòu)體會自動繼承匿名內(nèi)嵌變量的方法
}
type Apple struct {}
func (a Apple) eat() {
fmt.Println("eating apple")
}
type Banana struct {}
func (b Banana) eat() {
fmt.Println("eating banana")
}
func main() {
var f1 = Fruit{"Apple", Apple{}}
var f2 = Fruit{"Banana", Banana{}}
f1.want()
f2.want()
}
---------
I like eating apple
I like eating banana
使用這種方式模擬多態(tài)本質(zhì)上是通過組合屬性變量(Name)和接口變量(Fruitable)來做到的,屬性變量是對象的數(shù)據(jù),而接口變量是對象的功能,將它們組合到一塊就形成了一個(gè)完整的多態(tài)性的結(jié)構(gòu)體。
《GoInAction》第118頁也提供了一個(gè)例子:
// Sample program to show how polymorphic behavior with interfaces.
package main
import (
"fmt"
)
// notifier is an interface that defines notification
// type behavior.
type notifier interface {
notify()
}
// user defines a user in the program.
type user struct {
name string
email string
}
// notify implements the notifier interface with a pointer receiver.
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// admin defines a admin in the program.
type admin struct {
name string
email string
}
// notify implements the notifier interface with a pointer receiver.
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}
// main is the entry point for the application.
func main() {
// Create a user value and pass it to sendNotification.
bill := user{"Bill", "bill@email.com"}
sendNotification(&bill)
// Create an admin value and pass it to sendNotification.
lisa := admin{"Lisa", "lisa@email.com"}
sendNotification(&lisa)
}
// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
n.notify()
}
在第 53 行中,我們再次聲明了多態(tài)函數(shù) sendNotification,這個(gè)函數(shù)接受一個(gè)實(shí)現(xiàn)了notifier 接口的值作為參數(shù)。既然任意一個(gè)實(shí)體類型都能實(shí)現(xiàn)該接口,那么這個(gè)函數(shù)可以針對任意實(shí)體類型的值來執(zhí)行 notifier 方法。因此,這個(gè)函數(shù)就能提供多態(tài)的行為。
4.接口的組合繼承
接口的定義也支持組合繼承,比如我們可以將兩個(gè)接口定義合并為一個(gè)接口如下
type Smellable interface {
smell()
}
type Eatable interface {
eat()
}
type Fruitable interface {
Smellable
Eatable
}
這時(shí) Fruitable 接口就自動包含了 smell() 和 eat() 兩個(gè)方法,它和下面的定義是等價(jià)的。
type Fruitable interface {
smell()
eat()
}
5.接口變量的賦值
變量賦值本質(zhì)上是一次內(nèi)存淺拷貝,切片的賦值是拷貝了切片頭,字符串的賦值是拷貝了字符串的頭部,而數(shù)組的賦值呢是直接拷貝整個(gè)數(shù)組。接口變量的賦值會不會不一樣呢?接下來我們做一個(gè)實(shí)驗(yàn)
package main
import "fmt"
type Rect struct {
Width int
Height int
}
func main() {
var a interface {}
var r = Rect{50, 50}
a = r
var rx = a.(Rect)
r.Width = 100
r.Height = 100
fmt.Println(rx)
}
------
{50 50}
6.嵌入類型
《GoInAction》也提供了例子
// Sample program to show how to embed a type into another type and
// the relationship between the inner and outer type.
package main
import (
"fmt"
)
// user defines a user in the program.
type user struct {
name string
email string
}
// notify implements a method that can be called via
// a value of type user.
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// admin represents an admin user with privileges.
type admin struct {
user // Embedded Type
level string
}
// main is the entry point for the application.
func main() {
// Create an admin user.
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
},
level: "super",
}
// We can access the inner type's method directly.
ad.user.notify()
// The inner type's method is promoted.
ad.notify()
}
這展示了內(nèi)部類型是如何存在于外部類型內(nèi),并且總是可訪問的。不過,借助內(nèi)部類型提升,notify 方法也可以直接通過 ad 變量來訪問
再改造一下:
// notifier is an interface that defined notification
// type behavior.
type notifier interface {
notify()
}
// sendNotification accepts values that implement the notifier
// interface and sends notifications.
func sendNotification(n notifier) {
n.notify()
}
// main is the entry point for the application.
func main() {
// Create an admin user.
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
},
level: "super",
}
// Send the admin user a notification.
// The embedded inner type's implementation of the
// interface is "promoted" to the outer type.
sendNotification(&ad)
}
在代碼清單 5-58 的第 37 行,我們創(chuàng)建了一個(gè)名為 ad 的變量,其類型是外部類型 admin。這個(gè)類型內(nèi)部嵌入了 user 類型。之后第 48 行,我們將這個(gè)外部類型變量的地址傳給 sendNotification 函數(shù)。編譯器認(rèn)為這個(gè)指針實(shí)現(xiàn)了 notifier 接口,并接受了這個(gè)值的傳遞。不過如果看一下整個(gè)示例程序,就會發(fā)現(xiàn) admin 類型并沒有實(shí)現(xiàn)這個(gè)接口。由于內(nèi)部類型的提升,內(nèi)部類型實(shí)現(xiàn)的接口會自動提升到外部類型。這意味著由于內(nèi)部類型的實(shí)現(xiàn),外部類型也同樣實(shí)現(xiàn)了這個(gè)接口。
如果外部類型并不需要使用內(nèi)部類型的實(shí)現(xiàn),而想使用自己的一套實(shí)現(xiàn),該怎么辦?
// notify implements a method that can be called via
// a value of type Admin.
func (a *admin) notify() {
fmt.Printf("Sending admin email to %s<%s>\n",
a.name,
a.email)
}
// main is the entry point for the application.
func main() {
// Create an admin user.
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
},
level: "super",
}
// Send the admin user a notification.
// The embedded inner type's implementation of the
// interface is NOT "promoted" to the outer type.
sendNotification(&ad)
// We can access the inner type's method directly.
ad.user.notify()
// The inner type's method is NOT promoted.
ad.notify()
}
---------------------------------------------
Sending admin email to john smith<john@yahoo.com>
Sending user email to john smith<john@yahoo.com>
Sending admin email to john smith<john@yahoo.com>
這表明,如果外部類型實(shí)現(xiàn)了 notify 方法,內(nèi)部類型的實(shí)現(xiàn)就不會被提升。不過內(nèi)部類型的值一直存在,因此還可以通過直接訪問內(nèi)部類型的值,來調(diào)用沒有被提升的內(nèi)部類型實(shí)現(xiàn)的方法。
關(guān)于嵌套結(jié)構(gòu)體的應(yīng)用,可以在Golang sort自定義排序中看到
二、值接收和引用接收
在Golang 學(xué)習(xí)筆記六 函數(shù)function和方法method的區(qū)別講方法時(shí),有個(gè)例子,當(dāng)通過值或指針調(diào)用方法時(shí),go編譯器會自動幫我們處理方法接收者的不一致。
type user struct{
name string
email string
}
func (u user) printName(){
fmt.Printf("name: %s\n", u.name)
}
func (u *user) printEmail(){
fmt.Printf("email: %s\n", u.email)
}
func main() {
bill := user{"bill","bill@gmail.com"}
lisa := &user{"lisa","lisa@gmail.com"};
bill.printName()
lisa.printName()
bill.printEmail()
lisa.printEmail()
}
正常打?。?/p>
name: bill
name: lisa
email: bill@gmail.com
email: lisa@gmail.com
但是,如果通過接口類型的值調(diào)用方法,規(guī)則有很大不同:
在上面代碼中加上兩個(gè)接口
type printNamer interface{
printName()
}
type printEmailer interface{
printEmail()
}
func sendPrintName(n printNamer) {
n.printName()
}
func sendPrintEmail(n printEmailer){
n.printEmail()
}
func main() {
...
sendPrintName(bill)
sendPrintName(lisa)
sendPrintEmail(bill)
sendPrintEmail(lisa)
這里sendPrintEmail(bill)編譯不通過,提示:cannot use bill (type user) as type printEmailer in argument to sendPrintEmail:user does not implement printEmailer (printEmail method has pointer receiver)
觀察一下區(qū)別,bill是一個(gè)值,printEmail接收者是個(gè)指針,失敗了。但是另外一個(gè)不一致的卻能通過,那就是lisa是個(gè)指針,但是printName的接收者要求是值,為啥就能通過呢。
在《Go in Action》第118頁描述了方法集的規(guī)則:
使用指針作為接收者聲明的方法,只能在接口類型的值是一個(gè)指針的時(shí)候被調(diào)用。使用值作為接收者聲明的方法,在接口類型的值為值或者指針時(shí),都可以被調(diào)用。
為什么會有這種限制?事實(shí)上,編譯器并不是總能自動獲得一個(gè)值的地址,如代碼清單 5-46 所示。
代碼清單 5-46 listing46.go
01 // 這個(gè)示例程序展示不是總能
02 // 獲取值的地址
03 package main
04
05 import "fmt"
06
07 // duration 是一個(gè)基于 int 類型的類型
08 type duration int
09
10 // 使用更可讀的方式格式化 duration 值
11 func (d *duration) pretty() string {
12 return fmt.Sprintf("Duration: %d", *d)
13 }
14
15 // main 是應(yīng)用程序的入口
16 func main() {
17 duration(42).pretty()
18
19 // ./listing46.go:17: 不能通過指針調(diào)用 duration(42)的方法
20 // ./listing46.go:17: 不能獲取 duration(42)的地址
21 }
這里編譯通過,運(yùn)行也會報(bào)錯。我們改成變量調(diào)用就可以了:
dd := duration(42)
dd.pretty()