Golang接口類型-上篇
目錄
1、概述
2、接口的隱式實現(xiàn)
3、接口定義和聲明
4、接口類型賦值
5、接口類型對象
6、接口應(yīng)用舉例

1、概述
接口是計算機系統(tǒng)中多個組件共享的邊界,不同的組件能夠在邊界上交換信息。接口的本質(zhì)是引入一個新的中間層,調(diào)用方可以通過接口與具體實現(xiàn)分離,解除上下游的耦合,上層的模塊不再需要依賴下層的具體模塊,只需要依賴一個約定好的接口
簡單來說,Go語言中的接口就是一組方法的簽名。接口是Go語言整個類型系統(tǒng)的基石,其他語言的接口是不同組件之間的契約的存在,對契約的實現(xiàn)是強制性的,必須顯式聲明實現(xiàn)了該接口,這類接口稱之為“侵入式接口”。而Go語言的接口是隱式存在,只要實現(xiàn)了該接口的所有函數(shù)則代表已經(jīng)實現(xiàn)了該接口,并不需要顯式的接口聲明
接口的比喻?
一個常見的例子,電腦上只有一個USB接口。這個USB接口可以接MP3、數(shù)碼相機、攝像頭、鼠標(biāo)、鍵盤等。所有的上述硬件都可以公用這個接口,有很好的擴展性,該USB接口定義了一種規(guī)范,只要實現(xiàn)了該規(guī)范,就可以將不同的設(shè)備接入電腦,而設(shè)備的改變并不會對電腦本身有什么影響(低耦合)
接口表示調(diào)用者和設(shè)計者的一種約定,在多人合作開發(fā)同一個項目時,事先定義好相互調(diào)用的接口可以大大提高開發(fā)的效率。接口是用類來實現(xiàn)的,實現(xiàn)接口的類必須嚴(yán)格按照接口的聲明來實現(xiàn)接口提供的所有功能。有了接口,就可以在不影響現(xiàn)有接口聲明的情況下,修改接口的內(nèi)部實現(xiàn),從而使兼容性問題最小化
2、接口的隱式實現(xiàn)
在Java中實現(xiàn)接口需要顯式地聲明接口并實現(xiàn)所有方法,而在Go中實現(xiàn)接口的所有方法就隱式地實現(xiàn)了接口
定義接口需要使用interface關(guān)鍵字,在接口中只能定義方法簽名,不能包含成員變量,例如
type?error?interface?{
?Error()?string
}
如果一個類型需要實現(xiàn)error接口,那么它只需要實現(xiàn)Error() string方法,下面的RPCError結(jié)構(gòu)體就是 error接口的一個實現(xiàn)
type?RPCError?struct?{
?Code????int64
?Message?string
}
func?(e?*RPCError)?Error()?string?{
?return?fmt.Sprintf("%s,?code=%d",?e.Message,?e.Code)
}
會發(fā)現(xiàn)上述代碼根本就沒有error接口的影子,這正是因為Go語言中接口的實現(xiàn)都是隱式的
3、接口定義和聲明
接口是自定義類型,是對其他類型行為的抽象(定義一個接口類型,把其他類型的值賦值給自定義的接口)
接口定義使用interface標(biāo)識,聲明了一系列的函數(shù)簽名(函數(shù)名、函數(shù)參數(shù)、函數(shù)返回值)在定義接口時可以指定接口名稱,在后續(xù)聲明接口變量時使用
聲明接口變量只需要定義變量類型為接口名,此時變量被初始化為nil
package?main
import?"fmt"
type?Sender?interface?{
?Send(to?string,?msg?string)?error
?SendAll(tos?[]string,?msg?string)?error
}
func?main()??{
?var?sender?Sender
?fmt.Printf("%T?%v\n",?sender,?sender)??//??
}
4、接口類型賦值
為接口類型方法賦值,一般是定義一個結(jié)構(gòu)體,需要保證結(jié)構(gòu)體方法(方法名、參數(shù))均與接口中定義相同
package?main
import?"fmt"
type?Sender?interface?{
?Send(to?string,?msg?string)?error
?SendAll(tos?[]string,?msg?string)?error
}
type?EmailSender?struct?{
}
func?(s?EmailSender)?Send(to,?msg?string)?error?{
?fmt.Println("發(fā)送郵件給:",?to,?",消息內(nèi)容是:",?msg)
?return?nil
}
func?(s?EmailSender)?SendAll(tos?[]string,?msg?string)?error?{
?for?_,?to?:=?range?tos?{
??s.Send(to,?msg)
?}
?return?nil
}
func?main()?{
?var?sender?Sender?=?EmailSender{}
?fmt.Printf("%T?%v\n",?sender,?sender)?//??
?sender.Send("geek",?"早上好")
?sender.SendAll([]string{"aa","bb"},?"中午好")
}
使用接口的好處,概念上可能不好理解,來一個實際例子
package?main
import?"fmt"
type?Sender?interface?{
?Send(to?string,?msg?string)?error
?SendAll(tos?[]string,?msg?string)?error
}
type?EmailSender?struct?{
}
func?(s?EmailSender)?Send(to,?msg?string)?error?{
?fmt.Println("發(fā)送郵件給:",?to,?",消息內(nèi)容是:",?msg)
?return?nil
}
func?(s?EmailSender)?SendAll(tos?[]string,?msg?string)?error?{
?for?_,?to?:=?range?tos?{
??s.Send(to,?msg)
?}
?return?nil
}
type?SmsSender?struct?{
}
func?(s?SmsSender)?Send(to,?msg?string)?error?{
?fmt.Println("發(fā)送短信給:",?to,?",?消息內(nèi)容是:",?msg)
?return?nil
}
func?(s?SmsSender)?SendAll(tos?[]string,?msg?string)?error?{
?for?_,?to?:=?range?tos?{
??s.Send(to,?msg)
?}
?return?nil
}
//func?do(sender?EmailSender)?{
func?do(sender?Sender)?{
?sender.Send("領(lǐng)導(dǎo)",?"工作日志")
}
func?main()?{
?var?sender?Sender?=?EmailSender{}
?fmt.Printf("%T?%v\n",?sender,?sender)?//??
?sender.Send("geek",?"早上好")
?sender.SendAll([]string{"aa","bb"},?"中午好")
?do(sender)
?sender?=?SmsSender{}
?do(sender)
}
按照上面的示例,最后定義變量sender為接口類型Sender,調(diào)用接口方法時,只需要指定接口類型對應(yīng)的結(jié)構(gòu)體是什么,因為在定義接口時,已經(jīng)聲明了此接口實現(xiàn)了Send、SendAll兩個方法
var?sender?Sender?=?EmailSender{}
//?或
var?sender?Sender?=?SmsSender{}
//?單獨定義go函數(shù)調(diào)用
func?do(sender?Sender)?{
?sender.Send("領(lǐng)導(dǎo)",?"工作日志")
}
如果沒有接口,那么最終調(diào)用時,還需要對應(yīng)上其具體的結(jié)構(gòu)體類型,寫法為
var?sender?EmailSender?=?EmailSender{}
//?或
var?sender?SmsSender?=?SmsSender{}
//?單獨定義go函數(shù)調(diào)用
func?do(sender?EmailSender)?{
//?func?do(sender?SmsSender)?{
?sender.Send("領(lǐng)導(dǎo)",?"工作日志")
}
很明顯,前者使用接口定義變量,在傳參時也使用接口類型定義,在使用上更為簡單,僅僅只需要調(diào)整初始化的結(jié)構(gòu)體類型即可
5、接口類型對象
當(dāng)自定義類型實現(xiàn)了接口類型中聲明的所有函數(shù)時,則該類型的對象可以賦值給接口變量,并使用接口變量調(diào)用實現(xiàn)的接口
方法接收者全為值類型 如上面的例子
方法接收者全為指針類型
package?main
import?"fmt"
type?Sender?interface?{
?Send(to?string,?msg?string)?error
?SendAll(tos?[]string,?msg?string)?error
}
type?SmsSender?struct?{
}
func?(s?*SmsSender)?Send(to,?msg?string)?error?{
?fmt.Println("發(fā)送短信給:",?to,?",?消息內(nèi)容是:",?msg)
?return?nil
}
func?(s?*SmsSender)?SendAll(tos?[]string,?msg?string)?error?{
?for?_,?to?:=?range?tos?{
??s.Send(to,?msg)
?}
?return?nil
}
func?do(sender?Sender)?{
?sender.Send("領(lǐng)導(dǎo)",?"工作日志")
}
func?main()?{
?var?sender?Sender?=?&SmsSender{}??//?指針類型
?do(sender)
}
方法接收者既有值類型又有指針類型
WechatSender的send和sendAll,send有指針和值,sendAll只有指針,因此初始化的時候只能用指針,不能用值
package?main
import?"fmt"
type?Sender?interface?{
?Send(to?string,?msg?string)?error
?SendAll(tos?[]string,?msg?string)?error
}
type?WechatSender?struct?{
}
//?Send?接收者為值對象
func?(s?WechatSender)?Send(to,?msg?string)?error?{
?fmt.Println("發(fā)送微信給:",?to,?",?消息內(nèi)容是:",?msg)
?return?nil
}
//?SendAll?接收者為指針對象
func?(s?*WechatSender)?SendAll(tos?[]string,?msg?string)?error?{
?for?_,?to?:=?range?tos?{
??s.Send(to,?msg)
?}
?return?nil
}
//func?do(sender?EmailSender)?{
func?do(sender?Sender)?{
?sender.Send("領(lǐng)導(dǎo)",?"工作日志")
}
func?main()?{
?var?sender?Sender?=?&WechatSender{}
?do(sender)
}
當(dāng)接口(A)包含另外一個接口(B)中聲明的所有函數(shù)時(A 接口函數(shù)是 B 接口函數(shù)的父集,B 是 A 的子集),接口(A)的對象也可以賦值給其子集的接口(B)變量
package?main
import?"fmt"
type?SignalSender?interface?{
?Send(to,?msg?string)?error
}
type?Sender?interface?{
?Send(to?string,?msg?string)?error
?SendAll(tos?[]string,?msg?string)?error
}
...
func?main()?{
?var?ssender?SignalSender?=?sender??//?以接口的變量初始化另外一個接口
?ssender.Send("aa",?"你好")
}
若兩個接口聲明同樣的函數(shù)簽名,則這兩個接口完全等價 當(dāng)類型和父集接口賦值給接口變量時,只能調(diào)用接口變量定義接口中聲明的函數(shù)(方法)
6、接口應(yīng)用舉例
實際的生產(chǎn)例子,可以加深對接口的理解。例如多個數(shù)據(jù)源推送和查詢數(shù)據(jù)
package?main
import?(
?"fmt"
?"log"
)
/*
1、多個數(shù)據(jù)源
2、query方法查詢數(shù)據(jù)
3、pushdata方法寫入數(shù)據(jù)
?*/
type?DataSource?interface?{
?PushData(data?string)
?QueryData(name?string)?string
}
type?redis?struct?{
?Name?string
?Addr?string
}
func?(r?*redis)?PushData?(data?string)?{
?log.Printf("pushdata,name:%s,data:%s\n",?r.Name,data)
}
func?(r?*redis)?QueryData?(name?string)?string?{
?log.Printf("querydata,name:%s,data:%s\n",?r.Name,name)
?return?name?+?"redis"
}
type?kafka?struct?{
?Name?string
?Addr?string
}
func?(k?*kafka)?PushData?(data?string)?{
?log.Printf("pushdata,name:%s,data:%s\n",?k.Name,data)
}
func?(k?*kafka)?QueryData?(name?string)?string?{
?log.Printf("querydata,name:%s,data:%s\n",?k.Name,name)
?return?name?+?"kafka"
}
var?Dm?=?make(map[string]DataSource)
func?main()??{
?r:=redis{
??Name:?"redis",
??Addr:?"127.0.0.1",
?}
?k:=kafka{
??Name:"kafka",
??Addr:"192.169.0.1",
?}
?//?注冊數(shù)據(jù)源到承載的容器中
?Dm["redis"]?=?&r
?Dm["kafka"]?=?&k
?//?推送數(shù)據(jù)
?for?i:=0;i<5;i++{
??key:=fmt.Sprintf("key_%d",?i)
??for?_,ds:=range?Dm{
???ds.PushData(key)
??}
?}
?//?查詢數(shù)據(jù)
?for?i:=0;i<5;i++{
??key:=fmt.Sprintf("key_%d",?i)
??//r:=Dm["redis"]
??//r.QueryData(key)
??for?_,ds:=range?Dm{
???res:=ds.QueryData(key)
???log.Printf("query_from_ds,res:%s",?res)
??}
?}
}See you ~
參考資料
https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/
? 歡迎進(jìn)群一起進(jìn)行技術(shù)交流
? 加群方式:公眾號消息私信“加群”或加我好友再加群均可
