go 學習筆記之是否支持以及如何實現繼承

熟悉面向對象的小夥伴們可能會知道封裝,繼承和多態是最主要的特性,爲什麼前輩們會如此看重這三種特性,真的那麼重要嗎?

go-oop-inheritance-concept.jpeg

什麼是封裝

什麼是封裝,封裝有什麼好處以及怎麼實現封裝?

相信大多數小夥伴們都有自己的理解,簡而言之,言而簡之,封裝是屏蔽內部實現細節,僅僅對外暴露出有價值接口.

go-oop-inheritance-computer.jpeg

正如平時工作中使用的電腦一樣,我們並不關心電腦的內部組成,只要會開機關機等基本操作就能正常使用電腦,即便壞了的話,也是送去專業維修店進行修理,作爲使用者來說,我們從始至終並不關心電腦的工作原理以及它如何工作,唯一在乎的就是怎麼能夠上網.

go-oop-inheritance-computer-online.jpeg

之所以我們能如何方便地使用電腦進行網上衝浪,完全得益於電腦設計者對普通用戶屏蔽了無關細節,只暴露有價值的操作方法,這種實現方式就是封裝.

回到編程語言上,Go 語言作爲一種通用的編程語言,和其他主流的編程語言一樣支持封裝,Go 語言關於封裝的實現主要包括兩部分:

  • 數據結構的封裝
  • 行爲方法的控制

其中數據結構的封裝主要是使用結構體關鍵字 struct 實現,而行爲方法的控制是用首字母大小寫區分是否對外可見.

關於 Go 如何實現封裝的細節,可以參考前一篇文章: go 學習筆記之詳細說一說封裝是怎麼回事

繼承是怎麼回事

說完封裝,接着說一下繼承是怎麼回事?

繼承正是在封裝的基礎上逐步發展產生的概念,我們知道封裝是對某一類行爲事物模型的抽象,而這種抽象恰恰是由人定義實現的,因人而異也就導致了封裝並沒有統一的標準答案.

go-oop-inheritance-diff-anyone.jpeg

於是乎就可能存在着這麼一種情況,對於生活中常見的貓和狗,兩個人分別有兩種封裝方式:

A: 貓是一種寵物,淘氣可愛會賣萌,看家本領抓老鼠,偶爾還會喵喵喵.

go-oop-inheritance-one-cat.jpeg

B: 狗是一種寵物,忠實聽話能看家,嗅覺靈敏會破案,一言不合汪汪汪.

go-oop-inheritance-one-dog.jpeg

C: 我想要買一個寵物,文能賣萌,武可退敵,明個一早給我送來吧!

go-oop-inheritance-one-pet.jpeg

由於客戶C並沒有特別說明要的是貓還是狗,A以爲是貓,因爲貓安靜的時候很萌很可愛,生氣的時候用小爪爪撓你!
B以爲是狗,汪星人的名氣可不是自吹自擂,真的要惹怒了它,咬住不放能生生撕下一塊肉,你說這種武力值強不強大?

於是第二天,客戶C懵逼了,怎麼一下子送來了貓狗兩個寵物啊,可是又不能食言,因爲A和B說的都有道理啊!

go-oop-inheritance-no-word.jpeg

等 A和 B走了後,C望着送來的貓和狗,不禁陷入了深思:我只是想要一個寵物而已,非要給我貓和狗兩個選擇,我有選擇困難症啊!

go-oop-inheritance-hard-choose.jpeg

說完這個故事,剛開始會覺得有些滑稽,C只要隨便選擇一種寵物不就好了嗎,幹嘛非要全盤接收弄得自己不開心呢?

不知道正在看的讀者有沒有選擇困難症的經歷,面對多種選擇,一時不知道到底選擇哪一個,最終結果可能有三種情況:全盤肯定,全盤否定和部分肯定.

上述故事中,C選擇的是全盤肯定,A和B送來的貓和狗都是寵物,沒理由拒絕任何一個,於是C選擇全盤肯定,當然至於以後是否後悔只有 C自己心裏清楚.

前車之鑑可以爲師矣,如果下次我們也遇到這種情況應該如何選擇呢?

相信聰明的你心中已經有了自己的答案,大聲的說出來吧!我就要喵喵喵!

go-oop-inheritance-one-cat.jpeg

上述案例就好比同事間一起去吃飯,點餐時問你點些什麼?你說隨便!

原本是好意,以爲能自己這麼說能方便其他人,其實這種看似不挑剔的做法,對他人來說可能也是最難辦的事情.

因爲隨便的範圍太寬泛了,什麼都可以也就意味着很大可能上並不會令你滿意,你在方便別人的同時,他人也在想法設法取悅於你,不知道你的喜好,心裏就犯嘀咕了,這個他喜不喜歡吃呢?

所以,請不要再說隨便,勇敢亮出你的觀點態度,哪怕不被認可滿足.

故事中 A覺得貓是寵物,B覺得狗是寵物,因而當客戶真的要寵物時,A和 B都把各自的寵物送上去了,因此問題出現在寵物的定義上,到底什麼算是寵物?

其實C心中已經有了寵物的定義,那就是"文能賣萌,武可退敵",而貓和狗恰好都具備這種特性,因此對外宣傳時就不要再說這種默認的屬性,應該亮出自己的特色!

A: 貓會抓老鼠
B: 狗能認清路

C: 我要一種寵物,出現意外時能幫助到我的話,那就再好不過了!

如果一開始他們就這樣說的話,C真正需要的寵物到底是貓還是狗就一目瞭然了,也不會陷入選擇困難症了!

如何實現繼承

回到封裝的話題上來,轉換成編程語言就是A把貓封裝成寵物了,B把狗封裝成寵物了,而 C需要寵物時,貓和狗都是寵物,最終結果要麼是程序依舊正常運行,要麼是程序意外掛掉了,因爲很可能某一種寵物可能並不符合特定需求.

這種重複定義問題歸咎於封裝的標準不同,貓和狗兩者的封裝過程是獨立進行的,並沒有在一起商量着看看能不能繼續抽象出通用模型,混亂的封裝導致了貓是寵物,狗也是寵物.

這種多種相關概念的封裝很容易出現此類問題,所以適當進行統一分析繼續抽象出更高層次的封裝概念尤爲重要,基於此,原來的封裝就能從這種通用概念中解放出來,僅僅保留自己的特色就好,大大簡化了模型的語義.

普通封裝的概念和更高層次的抽象封裝概念的關係就是面向對象中的繼承,即貓繼承於寵物,表示貓不但擁有寵物的特點還有貓自己的亮點.

對於狗也是一樣,狗是寵物,狗也是狗自己本身,體現了自己的特點.

Go 語言和其他主流的面嚮對象語言有所不同,Go 並不支持繼承特性,因而也沒有單繼承,多繼承,重寫方法等複雜概念.

Go 是如何描述這種普通封裝和抽象封裝之間的關係呢?

肯定不是把貓定義成寵物,狗也定義成寵物那種方式!

Go 實現繼承的語義不是通過 extends 關鍵字而是通過結構體組合的方式,請看相關代碼.

  • 寵物就應該能文能武,這裏不關心結構體的字段,因而並沒有定義相關字段.
type Pet struct {
}

func (p *Pet) Skill() {
    fmt.Println("能文能武的寵物")
}
  • 貓是能夠抓老鼠的寵物,Go 採用組合的方式表達繼承的語義.
type Cat struct {
    p *Pet
}

func (c *Cat) Catch() {
    fmt.Println("老鼠天敵喵喵喵")
}
  • 狗是自帶導航功能的寵物,看我導盲犬的超能力!

type Dog struct {
    p *Pet
}

func (d *Dog) Navigate() {
    fmt.Println("自帶導航汪汪汪")
}

接下來,C開始檢驗貓和狗作爲寵物是否具備能文能武的基本要求,與此同時有沒有自身的特色?

func TestExtendInstance(t *testing.T) {
    p := new(Pet)

    d := new(Dog)
    d.p = p

    // 自帶導航汪汪汪
    d.Navigate()
    // 能文能武的寵物
    d.p.Skill()

    fmt.Println()

    c := new(Cat)
    c.p = p

    // 老鼠天敵喵喵喵
    c.Catch()
    // 能文能武的寵物
    c.p.Skill()
}

上述結果證明,Go 語言雖然不支持 extends 關鍵字表達的繼承特性,但是採用組合的方式也是可以實現繼承語義的,畢竟,黑貓還是白貓,抓住老鼠的纔是好貓!

面向對象的封裝和繼承暫時告一段落,下一篇文章中將開始講解接口,以及面向對象中最後的一個概念: 多態!

感謝你的閱讀,如果本文對你有所幫助,還望你能留言告訴我,也歡迎分享轉發喲!

雪之夢技術驛站.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章