Go 面試官問我如何實現物件導向?

煎魚發表於2021-10-08

大家好,我是煎魚。

在大家初識 Go 語言時,總會拿其他語言的基本特性來類比 Go 語言,說白了就是老知識和新知識產生關聯,實現更高的學習效率。

圖片

最常見的類比,就是 “Go 語言如何實現物件導向?”,進一步展開就是 Go 語言如何實現物件導向特性中的繼承。

這不僅在學習中才用到類比,在業內的 Go 面試中也有非常多的面試官喜歡問:

圖片

來自讀者微信群

在今天這篇文章中,煎魚帶大傢俱體展開了解這塊的知識。一起愉快地開始吸魚之路。

什麼是物件導向

在瞭解 Go 語言是不是物件導向(簡稱:OOP) 之前,我們必須先知道 OOP 是啥,得先給他 “下定義”。

根據 Wikipedia 的定義,我們梳理出 OOP 的幾個基本認知:

  • 物件導向程式設計(OOP)是一種基於 "物件" 概念的程式設計正規化,它可以包含資料和程式碼:資料以欄位的形式存在(通常稱為屬性或屬性),程式碼以程式的形式存在(通常稱為方法)。
  • 物件自己的程式可以訪問並經常修改自己的資料欄位。
  • 物件經常被定義為類的一個例項。
  • 物件利用屬性和方法的私有/受保護/公共可見性,物件的內部狀態受到保護,不受外界影響(被封裝)。

基於這幾個基本認知進行一步延伸出,物件導向的三大基本特性:

  • 封裝。
  • 繼承。
  • 多型。

至此對物件導向的基本概念講解結束,想更進一步瞭解的可自行網上衝浪。

Go 是物件導向的語言嗎

“Go 語言是否一門物件導向的語言?”,這是一個日經話題。官方 FAQ 給出的答覆是:

圖片

是的,也不是。原因是:

  • Go 有型別和方法,並且允許物件導向的程式設計風格,但沒有型別層次。
  • Go 中的 "介面 "概念提供了一種不同的方法,我們認為這種方法易於使用,而且在某些方面更加通用。還有一些方法可以將型別嵌入到其他型別中,以提供類似的東西,但不等同於子類。
  • Go 中的方法比 C++ 或 Java 中的方法更通用:它們可以為任何型別的資料定義,甚至是內建型別,如普通的、"未裝箱的 "整數。它們並不侷限於結構(類)。
  • Go 由於缺乏型別層次,Go 中的 "物件 "比 C++ 或 Java 等語言更輕巧。

Go 實現物件導向程式設計

封裝

物件導向中的 “封裝” 指的是可以隱藏物件的內部屬性和實現細節,僅對外提供公開介面呼叫,這樣子使用者就不需要關注你內部是怎麼實現的。

在 Go 語言中的屬性訪問許可權,通過首字母大小寫來控制:

  • 首字母大寫,代表是公共的、可被外部訪問的。
  • 首字母小寫,代表是私有的,不可以被外部訪問。

Go 語言的例子如下:

type Animal struct {
 name string
}

func NewAnimal() *Animal {
 return &Animal{}
}

func (p *Animal) SetName(name string) {
 p.name = name
}

func (p *Animal) GetName() string {
 return p.name
}

在上述例子中,我們宣告瞭一個結構體 Animal,其屬性 name 為小寫。沒法通過外部方法,在配套上存在 Setter 和 Getter 的方法,用於統一的訪問和設定控制。

以此實現在 Go 語言中的基本封裝。

繼承

物件導向中的 “繼承” 指的是子類繼承父類的特徵和行為,使得子類物件(例項)具有父類的例項域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。

圖片

圖來自網路

從實際的例子來看,就是動物是一個大父類,下面又能細分為 “食草動物”、“食肉動物”,這兩者會包含 “動物” 這個父類的基本定義。

在 Go 語言中,是沒有類似 extends 關鍵字的這種繼承的方式,在語言設計上採取的是組合的方式:

type Animal struct {
 Name string
}

type Cat struct {
 Animal
 FeatureA string
}

type Dog struct {
 Animal
 FeatureB string
}

在上述例子中,我們宣告瞭 CatDog 結構體,其在內部匿名組合了 Animal 結構體。因此 CatDog 的例項都可以呼叫 Animal 結構體的方法:

func main() {
 p := NewAnimal()
 p.SetName("煎魚,記得點贊~")

 dog := Dog{Animal: *p}
 fmt.Println(dog.GetName())
}

同時 CatDog 的例項可以擁有自己的方法:

func (dog *Dog) HelloWorld() {
 fmt.Println("腦子進煎魚了")
}

func (cat *Cat) HelloWorld() {
 fmt.Println("煎魚進腦子了")
}

上述例子能夠正常包含呼叫 Animal 的相關屬性和方法,也能夠擁有自己的獨立屬性和方法,在 Go 語言中達到了類似繼承的效果。

多型

物件導向中的 “多型” 指的同一個行為具有多種不同表現形式或形態的能力,具體是指一個類例項(物件)的相同方法在不同情形有不同表現形式。

多型也使得不同內部結構的物件可以共享相同的外部介面,也就是都是一套外部模板,內部實際是什麼,只要符合規格就可以。

在 Go 語言中,多型是通過介面來實現的:

type AnimalSounder interface {
 MakeDNA()
}

func MakeSomeDNA(animalSounder AnimalSounder) {
 animalSounder.MakeDNA()
}

在上述例子中,我們宣告瞭一個介面型別 AnimalSounder,配套一個 MakeSomeDNA 方法,其接受 AnimalSounder 介面型別作為入參。

因此在 Go 語言中。只要配套的 CatDog 的例項也實現了 MakeSomeDNA 方法,那麼我們就可以認為他是 AnimalSounder 介面型別:

type AnimalSounder interface {
 MakeDNA()
}

func MakeSomeDNA(animalSounder AnimalSounder) {
 animalSounder.MakeDNA()
}

func (c *Cat) MakeDNA() {
 fmt.Println("煎魚是煎魚")
}

func (c *Dog) MakeDNA() {
 fmt.Println("煎魚其實不是煎魚")
}

func main() {
 MakeSomeDNA(&Cat{})
 MakeSomeDNA(&Dog{})
}

CatDog 的例項實現了 AnimalSounder 介面型別的約束後,就意味著滿足了條件,他們在 Go 語言中就是一個東西。能夠作為入參傳入 MakeSomeDNA 方法中,再根據不同的例項實現多型行為。

總結

通過今天這篇文章,我們基本瞭解了物件導向的定義和 Go 官方對物件導向這一件事的看法,同時針對物件導向的三大特性:“封裝、繼承、多型” 在 Go 語言中的實現方法就進行了一一講解。

在日常工作中,基本瞭解這些概念就可以了。若是面試,可以針對三大特性:“封裝、繼承、多型” 和 五大原則 “單一職責原則(SRP)、開放封閉原則(OCP)、里氏替換原則(LSP)、依賴倒置原則(DIP)、介面隔離原則(ISP)” 進行深入理解和說明。

在說明後針對上述提到的概念。再在 Go 語言中講解其具體的實現和利用到的基本原理,互相結合講解,就能得到一個不錯的效果了。

鼓勵

若有任何疑問歡迎評論區反饋和交流,最好的關係是互相成就,各位的點贊就是煎魚創作的最大動力,感謝支援。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,歡迎 Star 催更。

參考

  • Is Go an Object Oriented language?
  • 物件導向的三大基本特徵,五大基本原則
  • Go 物件導向程式設計(譯)

相關文章