<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Golang接口類型-上篇

          共 6025字,需瀏覽 13分鐘

           ·

          2021-10-24 23:17

          目錄

          • 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)了SendSendAll兩個方法

          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)
          }
          • 方法接收者既有值類型又有指針類型

          WechatSendersendsendAllsend有指針和值,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 ~

          參考資料

          [1]

          https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/

          ? 歡迎進(jìn)群一起進(jìn)行技術(shù)交流

          ? 加群方式:公眾號消息私信“加群或加我好友再加群均可

          瀏覽 107
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  囯产精品久久久久久久久久辛辛 | 一级黄片免费看 | 99在线免费视频观看 | 天天看av| 大香蕉一本线 |