<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ū)ο缶幊唐ㄈ和ㄟ^(guò)組合實(shí)現(xiàn)類的繼承和方法重寫

          共 5215字,需瀏覽 11分鐘

           ·

          2021-03-07 14:51

          一、概述

          在前面兩篇教程中,學(xué)院君已經(jīng)介紹了 Go 語(yǔ)言不像 Java、PHP 等支持面向編程的語(yǔ)言那樣,支持 class 之類的關(guān)鍵字來(lái)定義類,而是通過(guò) type 關(guān)鍵字結(jié)合基本類型或者結(jié)構(gòu)體來(lái)自定義類型系統(tǒng),此外,它也不支持通過(guò) extends 關(guān)鍵字來(lái)顯式定義類型之間的繼承關(guān)系。

          所以,嚴(yán)格來(lái)說(shuō),Go 語(yǔ)言并不是一門面向?qū)ο缶幊陶Z(yǔ)言,至少不是面向?qū)ο缶幊痰淖罴堰x擇(Java 才是最根正苗紅的),不過(guò)我們可以基于它提供的一些特性來(lái)模擬實(shí)現(xiàn)面向?qū)ο缶幊獭?/p>

          要實(shí)現(xiàn)面向?qū)ο缶幊?,就必須?shí)現(xiàn)面向?qū)ο缶幊痰娜筇匦裕悍庋b、繼承和多態(tài)。

          二、封裝

          首先是封裝,這一點(diǎn)我們?cè)?a target="_blank" textvalue="上篇教程" data-itemshowtype="0" tab="innerlink" data-linktype="2">上篇教程中已經(jīng)詳細(xì)介紹過(guò):將函數(shù)定義為歸屬某個(gè)自定義類型,這就等同于實(shí)現(xiàn)了類的成員方法,如果這個(gè)自定義類型是基于結(jié)構(gòu)體的,那么結(jié)構(gòu)體的字段可以看做是類的屬性。

          三、繼承

          然后是繼承,Go 雖然沒有直接提供繼承相關(guān)的語(yǔ)法實(shí)現(xiàn),但是我們通過(guò)組合的方式間接實(shí)現(xiàn)類似功能,所謂組合,就是將一個(gè)類型嵌入到另一個(gè)類型,從而構(gòu)建新的類型結(jié)構(gòu)。

          傳統(tǒng)面向?qū)ο缶幊讨?,顯式定義繼承關(guān)系的弊端有兩個(gè):一個(gè)是導(dǎo)致類的層級(jí)越來(lái)越復(fù)雜,另一個(gè)是影響了類的擴(kuò)展性,很多軟件設(shè)計(jì)模式的理念就是通過(guò)組合來(lái)替代繼承提高類的擴(kuò)展性。

          我們來(lái)看一個(gè)例子,現(xiàn)在有一個(gè) Animal 結(jié)構(gòu)體類型,它有一個(gè)屬性 Name 用于表示該動(dòng)物的名稱,以及三個(gè)成員方法,分別用來(lái)獲取動(dòng)物叫聲、喜歡的食物和動(dòng)物的名稱:

          type Animal struct {
              Name string
          }

          func (a Animal) Call() string {
              return "動(dòng)物的叫聲..."
          }

          func (a Animal) FavorFood() string {
              return "愛吃的食物..."
          }

          func (a Animal) GetName() string  {
              return a.Name
          }

          如果我們要定義一個(gè)繼承自該類型的子類 Dog,可以這么做:

          type Dog struct {
              Animal
          }

          這里,我們?cè)?Dog 結(jié)構(gòu)體類型中,嵌入了 Animal 這個(gè)類型,這樣一來(lái),我們就可以在 Dog 實(shí)例上訪問(wèn)所有 Animal 類型包含的屬性和方法:

          func main() {
              animal := Animal{"中華田園犬"}
              dog := Dog{animal}

              fmt.Println(dog.GetName())
              fmt.Println(dog.Call())
              fmt.Println(dog.FavorFood())
          }

          上述代碼的打印結(jié)果如下:

          中華田園犬
          動(dòng)物的叫聲...
          愛吃的食物...

          這就相當(dāng)于通過(guò)組合實(shí)現(xiàn)了類與類之間的繼承功能。

          四、多態(tài)

          此外,我們還可以通過(guò)在子類中定義同名方法來(lái)覆蓋父類方法的實(shí)現(xiàn),在面向?qū)ο缶幊讨羞@一術(shù)語(yǔ)叫做方法重寫,比如在上述 Dog 類型中,我們可以重寫 Call 方法和 FavorFood 方法的實(shí)現(xiàn)如下:

          func (d Dog) FavorFood() string {
              return "骨頭"
          }

          func (d Dog) Call() string {
              return "汪汪汪"
          }

          當(dāng)我們?cè)賵?zhí)行 main 函數(shù)時(shí),直接在 Dog 實(shí)例上調(diào)用 Call 方法或 FavorFood 方法時(shí),調(diào)用的就是 Dog 類中定義的方法而不是 Animal 中定義的方法:

          -w681

          當(dāng)然,你可以可以像這樣繼續(xù)調(diào)用父類 Animal 中的方法:

          fmt.Print(dog.Animal.Call())
          fmt.Println(dog.Call())
          fmt.Print(dog.Animal.FavorFood())
          fmt.Println(dog.FavorFood())

          只不過(guò) Go 語(yǔ)言不同于 Java、PHP 等面向?qū)ο缶幊陶Z(yǔ)言,沒有專門提供引用父類實(shí)例的關(guān)鍵字罷了(super、parent 等),在 Go 語(yǔ)言中,設(shè)計(jì)哲學(xué)一切從簡(jiǎn),沒有一個(gè)多余的關(guān)鍵字,所有的調(diào)用都是所見即所得。

          這種同一個(gè)方法在不同情況下具有不同的表現(xiàn)方式,就是多態(tài),在傳統(tǒng)面向?qū)ο缶幊讨?,多態(tài)還有另一個(gè)非常常見的使用場(chǎng)景 —— 類對(duì)接口的實(shí)現(xiàn),Go 語(yǔ)言也支持此功能,關(guān)于這一塊我們放到后面接口部分單獨(dú)介紹。

          五、更多細(xì)節(jié)

          可以看到,與傳統(tǒng)面向?qū)ο缶幊陶Z(yǔ)言的繼承機(jī)制不同,這種組合的實(shí)現(xiàn)方式更加靈活,我們不用考慮單繼承還是多繼承,你想要繼承哪個(gè)類型的方法,直接組合進(jìn)來(lái)就好了。

          多繼承同名方法沖突處理

          需要注意組合的不同類型之間包含同名方法,比如 AnimalPet 都包含了 GetName 方法,如果子類 Dog 沒有重寫該方法,直接在 Dog 實(shí)例上調(diào)用的話會(huì)報(bào)錯(cuò):

          ...

          type Pet struct {
              Name string
          }

          func (p Pet) GetName() string  {
              return p.Name
          }

          type Dog struct {
              Animal
              Pet
          }

          ...

          func main() {
              animal := Animal{"中華田園犬"}
              pet := Pet{"寵物狗"}
              dog := Dog{animal, pet}

              fmt.Println(dog.GetName())

              ...

          }

          執(zhí)行上述代碼會(huì)報(bào)錯(cuò):

          # command-line-arguments
          chapter04/03-compose.go:49:17: ambiguous selector dog.GetName

          除非你顯式指定調(diào)用哪個(gè)父類的方法:

          fmt.Println(dog.Pet.GetName())

          調(diào)整組合位置改變內(nèi)存布局

          另外,我們還可以通過(guò)任意調(diào)整被組合類型的位置來(lái)改變類的內(nèi)存布局:

          type Dog struct {
              Animal
              Pet
          }

          type Dog struct {
             Pet
             Animal
          }

          雖然上面兩個(gè) Dog 子類的功能一致,但是它們的內(nèi)存結(jié)構(gòu)不同。

          繼承指針類型的屬性和方法

          當(dāng)然,在 Go 語(yǔ)言中,你還可以以指針方式繼承某個(gè)類型的屬性和方法:

          type Dog struct { 
              *Animal
          }

          這種情況下,除了傳入 Animal 實(shí)例的時(shí)候要傳入指針引用之外,其它調(diào)用無(wú)需修改:

          func main() {
              animal := Animal{"中華田園犬"}
              pet := Pet{"寵物狗"}
              dog := Dog{&amp;animal, pet}

              fmt.Println(dog.Animal.GetName())
              fmt.Print(dog.Animal.Call())
              fmt.Println(dog.Call())
              fmt.Print(dog.Animal.FavorFood())
              fmt.Println(dog.FavorFood())
          }

          當(dāng)我們通過(guò)組合實(shí)現(xiàn)類之間的繼承時(shí),由于結(jié)構(gòu)體實(shí)例本身是值類型,如果傳入值字面量的話,實(shí)際上傳入的是結(jié)構(gòu)體實(shí)例的副本,對(duì)內(nèi)存耗費(fèi)更大,所以組合指針類型性能更好

          為組合類型設(shè)置別名

          前面的示例調(diào)用父類方法時(shí)都直接引用的是組合類型(父類)的類型字面量,其實(shí),我們還可以像基本類型一樣,為其設(shè)置別名,方便引用:

          type Dog struct {
              animal *Animal
              pet Pet
          }

          ...

          func main() {
              animal := Animal{"中華田園犬"}
              pet := Pet{"寵物狗"}
              dog := Dog{&amp;animal, pet}

             // 通過(guò) animal 引用 Animal 類型實(shí)例 
              fmt.Println(dog.animal.GetName())
              fmt.Print(dog.animal.Call())
              fmt.Println(dog.Call())
              fmt.Print(dog.animal.FavorFood())
              fmt.Println(dog.FavorFood())
          }

          關(guān)于 Go 語(yǔ)言如何通過(guò)組合實(shí)現(xiàn)類與類之間的繼承和方法重寫,學(xué)院君就簡(jiǎn)單介紹到這里,下篇教程,我們一起來(lái)看看 Go 語(yǔ)言是如何管理類屬性和方法的可見性的。

          本篇教程的源碼可以在 Github 代碼倉(cāng)庫(kù)獲?。篽ttps://github.com/nonfu/golang-tutorial/blob/main/chapter04/03-compose.go。

          (本文完)


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


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

          瀏覽 82
          點(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>
                  人人艹人人摸 | 大香蕉在线69 | 欧美大香蕉中文 | 最新亚洲视频在线观看 | 草逼网视频 |