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