<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>

          Go 面向?qū)ο缶幊唐ㄎ澹航涌诙x及實(shí)現(xiàn)

          共 7228字,需瀏覽 15分鐘

           ·

          2021-03-07 14:50

          接口在 Go 語(yǔ)言中有著至關(guān)重要的地位,如果說(shuō) goroutine 和 channel 是支撐起 Go 語(yǔ)言并發(fā)模型的基石,那么接口就是 Go 語(yǔ)言整個(gè)類型系統(tǒng)的基石。Go 語(yǔ)言的接口不單單只是接口,下面就讓我們一步步來(lái)探索 Go 語(yǔ)言的接口特性。

          一、傳統(tǒng)侵入式接口實(shí)現(xiàn)

          和類的實(shí)現(xiàn)相似,Go 語(yǔ)言的接口和其他語(yǔ)言中提供的接口概念完全不同。以 Java、PHP 為例,接口主要作為不同類之間的契約(Contract)存在,對(duì)契約的實(shí)現(xiàn)是強(qiáng)制的,體現(xiàn)在具體的細(xì)節(jié)上就是如果一個(gè)類實(shí)現(xiàn)了某個(gè)接口,就必須實(shí)現(xiàn)該接口聲明的所有方法,這個(gè)叫「履行契約」:

          // 聲明一個(gè)'iTemplate'接口
          interface iTemplate
          {
              public function setVariable($name, $var);
              public function getHtml($template);
          }


          // 實(shí)現(xiàn)接口
          // 下面的寫法是正確的
          class Template implements iTemplate
          {
              private $vars = array();

              public function setVariable($name, $var)
              
          {
                  $this->vars[$name] = $var;
              }

              public function getHtml($template)
              
          {
                  foreach($this->vars as $name => $value) {
                      $template = str_replace('{' . $name . '}', $value, $template);
                  }

                  return $template;
              }
          }

          這個(gè)時(shí)候,如果有另外有一個(gè)接口 iTemplate2 聲明了與 iTemplate 完全一樣的接口方法,甚至名字也叫 iTemplate,只不過(guò)位于不同的命名空間下,編譯器也會(huì)認(rèn)為上面的類 Template 只實(shí)現(xiàn)了 iTemplate 而沒(méi)有實(shí)現(xiàn) iTemplate2 接口。

          這在我們之前的認(rèn)知中是理所當(dāng)然的,無(wú)論是類與類之間的繼承,還是類與接口之間的實(shí)現(xiàn),在 Java、PHP 這種單繼承語(yǔ)言中,存在著嚴(yán)格的層級(jí)關(guān)系,一個(gè)類只能直接繼承自一個(gè)父類,一個(gè)類也只能實(shí)現(xiàn)指定的接口,如果沒(méi)有顯式聲明繼承自某個(gè)父類或者實(shí)現(xiàn)某個(gè)接口,那么這個(gè)類就與該父類或者該接口沒(méi)有任何關(guān)系。

          我們把這種接口稱為侵入式接口,所謂「侵入式」指的是實(shí)現(xiàn)類必須明確聲明自己實(shí)現(xiàn)了某個(gè)接口。這種實(shí)現(xiàn)方式雖然足夠明確和簡(jiǎn)單明了,但也存在一些問(wèn)題,尤其是在設(shè)計(jì)標(biāo)準(zhǔn)庫(kù)的時(shí)候,因?yàn)闃?biāo)準(zhǔn)庫(kù)必然涉及到接口設(shè)計(jì),接口的需求方是業(yè)務(wù)實(shí)現(xiàn)類,只有具體編寫業(yè)務(wù)實(shí)現(xiàn)類的時(shí)候才知道需要定義哪些方法,而在此之前,標(biāo)準(zhǔn)庫(kù)的接口就已經(jīng)設(shè)計(jì)好了,我們要么按照約定好的接口進(jìn)行實(shí)現(xiàn),如果沒(méi)有合適的接口需要自己去設(shè)計(jì),這里的問(wèn)題就是接口的設(shè)計(jì)和業(yè)務(wù)的實(shí)現(xiàn)是分離的,接口的設(shè)計(jì)者并不能總是預(yù)判到業(yè)務(wù)方要實(shí)現(xiàn)哪些功能,這就造成了設(shè)計(jì)與實(shí)現(xiàn)的脫節(jié)。

          接口的過(guò)分設(shè)計(jì)會(huì)導(dǎo)致某些聲明的方法實(shí)現(xiàn)類完全不需要,如果設(shè)計(jì)的太簡(jiǎn)單又會(huì)導(dǎo)致無(wú)法滿足業(yè)務(wù)的需求,這確實(shí)是一個(gè)問(wèn)題,而且脫離了用戶使用場(chǎng)景討論這些并沒(méi)有意義,以 PHP 自帶的 SessionHandlerInterface 接口為例,該接口聲明的接口方法如下:

          SessionHandlerInterface {
              /* 方法 */
              abstract public close ( void ) : bool
              abstract public destroy ( string $session_id ) : bool
              abstract public gc ( int $maxlifetime ) : int
              abstract public open ( string $save_path , string $session_name ) : bool
              abstract public read ( string $session_id ) : string
              abstract public write ( string $session_id , string $session_data ) : bool
          }

          用戶自定義的 Session 管理器需要實(shí)現(xiàn)該接口,也就是要實(shí)現(xiàn)該接口聲明的所有方法,但是實(shí)際在做業(yè)務(wù)開(kāi)發(fā)的時(shí)候,某些方法其實(shí)并不需要實(shí)現(xiàn),比如如果我們基于 Redis 或 Memcached 作為 Session 存儲(chǔ)器的話,它們自身就包含了過(guò)期回收機(jī)制,所以 gc 方法根本不需要實(shí)現(xiàn),又比如 close 方法對(duì)于大部分驅(qū)動(dòng)來(lái)說(shuō),也是沒(méi)有什么意義的。

          正是因?yàn)檫@種不合理的設(shè)計(jì),所以在編寫 PHP 類庫(kù)中的每個(gè)接口時(shí)都需要糾結(jié)以下兩個(gè)問(wèn)題(Java 也類似):

          1. 一個(gè)接口需要聲明哪些接口方法?

          2. 如果多個(gè)類實(shí)現(xiàn)了相同的接口方法,應(yīng)該如何設(shè)計(jì)接口?比如上面這個(gè) SessionHandlerInterface,有沒(méi)有必要拆分成多個(gè)更細(xì)分的接口,以適應(yīng)不同實(shí)現(xiàn)類的需要?

          接下我們來(lái)看看 Go 語(yǔ)言的接口是如何避免這些問(wèn)題的。

          二、Go 語(yǔ)言的接口實(shí)現(xiàn)

          在 Go 語(yǔ)言中,類對(duì)接口的實(shí)現(xiàn)和子類對(duì)父類的繼承一樣,并沒(méi)有提供類似 implement 這種關(guān)鍵字顯式聲明該類實(shí)現(xiàn)了哪個(gè)接口,一個(gè)類只要實(shí)現(xiàn)了某個(gè)接口要求的所有方法,我們就說(shuō)這個(gè)類實(shí)現(xiàn)了該接口。

          例如,我們定義了一個(gè) File 類,并實(shí)現(xiàn)了 Read()、Write()Seek()、Close() 四個(gè)方法:

          type File struct { 
              // ...
          }

          func (f *File) Read(buf []byte) (n int, err error) 
          func (f *File) Write(buf []byte) (n int, err error) 
          func (f *File) Seek(off int64, whence int) (pos int64, err error) 
          func (f *File) Close() error

          假設(shè)我們有如下接口(Go 語(yǔ)言通過(guò)關(guān)鍵字 interface 來(lái)聲明接口,以示和結(jié)構(gòu)體類型的區(qū)別,花括號(hào)內(nèi)包含的是待實(shí)現(xiàn)的方法集合):

          type IFile interface { 
              Read(buf []byte) (n int, err error) 
              Write(buf []byte) (n int, err error) 
              Seek(off int64, whence int) (pos int64, err error) 
              Close() error 
          }

          type IReader interface { 
              Read(buf []byte) (n int, err error) 
          }

          type IWriter interface { 
              Write(buf []byte) (n int, err error) 
          }

          type ICloser interface { 
              Close() error 
          }

          盡管 File 類并沒(méi)有顯式實(shí)現(xiàn)這些接口,甚至根本不知道這些接口的存在,但是我們說(shuō) File 類實(shí)現(xiàn)了這些接口,因?yàn)?File 類實(shí)現(xiàn)了上述所有接口聲明的方法。當(dāng)一個(gè)類的成員方法集合包含了某個(gè)接口聲明的所有方法,換句話說(shuō),如果一個(gè)接口的方法集合是某個(gè)類成員方法集合的子集,我們就認(rèn)為該類實(shí)現(xiàn)了這個(gè)接口。

          與 Java、PHP 相對(duì),我們把 Go 語(yǔ)言的這種接口稱作非侵入式接口,因?yàn)轭惻c接口的實(shí)現(xiàn)關(guān)系不是通過(guò)顯式聲明,而是系統(tǒng)根據(jù)兩者的方法集合進(jìn)行判斷。這樣做有兩個(gè)好處:

          • 其一,Go 語(yǔ)言的標(biāo)準(zhǔn)庫(kù)不需要繪制類庫(kù)的繼承/實(shí)現(xiàn)樹(shù)圖,在 Go 語(yǔ)言中,類的繼承樹(shù)并無(wú)意義,你只需要知道這個(gè)類實(shí)現(xiàn)了哪些方法,每個(gè)方法是干什么的就足夠了。

          • 其二,定義接口的時(shí)候,只需要關(guān)心自己應(yīng)該提供哪些方法即可,不用再糾結(jié)接口需要拆得多細(xì)才合理,也不需要為了實(shí)現(xiàn)某個(gè)接口而引入接口所在的包,接口由使用方按需定義,不用事先設(shè)計(jì),也不用考慮之前是否有其他模塊定義過(guò)類似接口。

          這樣一來(lái),就完美地避免了傳統(tǒng)面向?qū)ο缶幊讨械慕涌谠O(shè)計(jì)問(wèn)題。

          三、通過(guò)組合實(shí)現(xiàn)接口繼承

          我們知道在 Java、PHP 等傳統(tǒng)面向?qū)ο缶幊陶Z(yǔ)言中,支持通過(guò) extends 關(guān)鍵字實(shí)現(xiàn)接口之間的繼承關(guān)系:

          interface A 
          {
              public function foo();
          }

          interface B extends A
          {
              public function bar();
          }

          在上述代碼中,我們定義了兩個(gè) PHP 接口:AB,其中接口 B 繼承自 A,這樣一來(lái),如果某個(gè)類實(shí)現(xiàn)了接口 B,則必須實(shí)現(xiàn)這兩個(gè)接口中聲明的方法,否則會(huì)報(bào)錯(cuò)。

          Go 語(yǔ)言也支持類似的「接口繼承」特性,但是由于不支持 extends 關(guān)鍵字,所以其實(shí)現(xiàn)和類的繼承一樣,是通過(guò)組合來(lái)完成的。以上面這個(gè) PHP 示例為例,在 Go 語(yǔ)言中,我們可以這樣通過(guò)接口組合來(lái)實(shí)現(xiàn)接口繼承,就像類的組合一樣:

          type A interface {
              Foo()
          }

          type B interface {
              A
              Bar()
          }

          然后我們定義一個(gè)類 T 實(shí)現(xiàn)接口 B

          type T struct {}

          func (t T) Foo() {
              fmt.Println("call Foo function from interface A.")
          }

          func (t T) Bar() {
              fmt.Println("call Bar function from interface B.")
          }

          不過(guò),在 Go 語(yǔ)言中,又與傳統(tǒng)的接口繼承有些不同,因?yàn)榻涌趯?shí)現(xiàn)不是強(qiáng)制的,是根據(jù)類實(shí)現(xiàn)的方法來(lái)動(dòng)態(tài)判定的,比如我們上面的 T 類可以只實(shí)現(xiàn) Foo 方法,也可以只實(shí)現(xiàn) Bar 方法,也可以都不實(shí)現(xiàn)。如果只實(shí)現(xiàn)了 Foo 方法,則 T 實(shí)現(xiàn)了接口 A;如果只實(shí)現(xiàn)了 Bar 方法,則既沒(méi)有實(shí)現(xiàn)接口 A 也沒(méi)有實(shí)現(xiàn)接口 B,只有兩個(gè)方法都實(shí)現(xiàn)了系統(tǒng)才會(huì)判定實(shí)現(xiàn)了接口 B。

          可以認(rèn)為接口組合是匿名類型組合(沒(méi)有顯式為組合類型設(shè)置對(duì)應(yīng)的屬性名稱)的一個(gè)特定場(chǎng)景,只不過(guò)接口只包含方法,而不包含任何屬性。Go 語(yǔ)言底層很多包就是基于接口組合實(shí)現(xiàn)的,比如 io 里面的 Reader、Writer、ReadWriter 這些接口:

          // Reader is the interface that wraps the basic Read method.
          type Reader interface {
              Read(p []byte) (n int, err error)
          }

          // Writer is the interface that wraps the basic Write method.
          type Writer interface {
              Write(p []byte) (n int, err error)
          }

          // ReadWriter is the interface that groups the basic Read and Write methods.
          type ReadWriter interface {
              Reader
              Writer
          }


          (本文完)


          學(xué)習(xí)過(guò)程中有任何問(wèn)題,可以通過(guò)下面的評(píng)論功能或加入「Go 語(yǔ)言研習(xí)社」與學(xué)院君討論:


          本系列教程首發(fā)在 geekr.dev,你可以點(diǎn)擊頁(yè)面左下角閱讀原文鏈接查看最新更新的教程。

          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          <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>
                  欧美综合激情网 | 中国黄色在线视频 | 亚洲婷婷夜色 | 不戴套进入让少妇高潮 | 亚洲欧洲在线看 |