前言
我的讀者中應該大部分都是 Java
從業者,不知道寫 Java
這些年是否真的有找到物件?
沒找到也沒關係,總不能在一棵樹上吊死,我們也可以來 Go
這邊看看,說不定會有新發現。
開個玩笑,本文會以一個 Javaer
的角度來聊聊 Go
語言中的物件導向。
OOP
物件導向這一詞來源於Object Oriented Programming
,也就是大家常說的 OOP
。
對於 Go
是否為物件導向的程式語言,這點也是討論已久;不過我們可以先看看官方的說法:
其他的我們暫且不看,Yes and No.
這個回答就比較微妙了,為了這篇文章還能寫下去我們先認為 Go
是物件導向的。
物件導向有著三個重要特徵:
- 封裝
- 繼承
- 多型
封裝
Go
並沒有 Class
的概念,卻可以使用 struct
來達到類似的效果,比如我們可以對汽車宣告如下:
type Car struct {
Name string
Price float32
}
與 Java
不同的是,struct
中只儲存資料,不能定義行為,也就是方法。
當然也能為 Car
定義方法,只是寫法略有不同:
func (car *Car) Info() {
fmt.Printf("%v price: [%v]", car.Name, car.Price)
}
func main() {
car := Car{
Name: "BMW",
Price: 100.0,
}
car.Info()
}
在方法名稱前加上 (car *Car)
便能將該方法指定給 Car
,其中的 car
引數可以理解為 Java
中的 this
以及 Python
中的 self
,就語義來說我覺得 go
更加簡單一些。
畢竟我見過不少剛學習 Java
的萌新非常不理解 this
的含義與用法。
匿名結構體
既然談到結構體了那就不得不聊聊 Go
支援的匿名結構體(雖然和麵向物件沒有太大關係)
func upload(path string) {
body, err := ioutil.ReadAll(res.Body)
smsRes := struct {
Success bool `json:"success"`
Code string `json:"code"`
Message string `json:"message"`
Data struct {
URL string `json:"url"`
} `json:"data"`
RequestID string `json:"RequestId"`
}{}
err = json.Unmarshal(body, &smsRes)
fmt.Printf(smsRes.Message)
}
Go
允許我們在方法內部建立一個匿名的結構體,後續還能直接使用該結構體來獲取資料。
這點在我們呼叫外部介面解析響應資料時非常有用,建立一個臨時的結構體也不用額為維護;同時還能用物件導向的方式獲取資料。
相比於將資料存放在 map
中用欄位名獲取要優雅許多。
繼承
Go
語言中並沒有 Java
、C++
這樣的繼承概念,類之間的關係更加扁平簡潔。
各位 Javaer
應該都看過這類圖:
相信大部分新手看到這圖時就已經懵逼,更別說研究各個類之間的關係了。
不過這樣好處也明顯:如果我們抽象合理,整個系統結構會很好維護和擴充套件;但前提是我們能抽象合理。
在 Go
語言中更推薦使用組合的方式來複用資料:
type ElectricCar struct {
Car
Battery int32
}
func main() {
xp := ElectricCar{
Car{Name: "xp", Price: 200},
70,
}
fmt.Println(xp.Name)
}
這樣我們便可以將公共部分的資料組合到新的 struct
中,並能夠直接使用。
介面(多型)
面向介面程式設計的好處這裡就不在贅述了,我們來看看 Go 是如何實現的:
type ElectricCar struct {
Car
Battery int32
}
type PetrolCar struct {
Car
Gasoline int32
}
//定義一個介面
type RunService interface {
Run()
}
// 實現1
func (car *PetrolCar) Run() {
fmt.Printf("%s PetrolCar run \n", car.Name)
}
// 實現2
func (car *ElectricCar)Run() {
fmt.Printf("%s ElectricCar run \n", car.Name)
}
func Do(run RunService) {
run.Run()
}
func main() {
xp := ElectricCar{
Car{Name: "xp", Price: 200},
70,
}
petrolCar := PetrolCar{
Car{Name: "BMW", Price: 300},
50,
}
Do(&xp)
Do(&petrolCar)
}
首先定義了一個介面 RunService
;ElectricCar
與 PetrolCar
都實現了該介面。
可以看到 Go
實現一個介面的方式並不是 implement
,而是用結構體宣告一個相同簽名的方法。
這種實現模式被稱為”鴨子型別“,Python
中的介面也是類似的鴨子型別
。
詳細介紹可以參考這篇:Python 中的面向介面程式設計
介面當然也是可以擴充套件的,類似於 struct
中的巢狀:
type DiService interface {
Di()
}
//定義一個介面
type RunService interface {
DiService
Run()
}
得益於 Go
的強型別,剛才的 struct
也得實現 DiService
這個介面才能編譯通過。
總結
到這裡應該是能理解官方所說的 Yes and No.
的含義了;Go
對物件導向的語法不像 Java
那麼嚴苛,甚至整個語言中都找不到 object(物件)
這個關鍵詞;但是利用 Go
裡的其他特性也是能實現 OOP
的。
是否為物件導向我覺得並不重要,主要目的是我們能寫出易擴充套件好維護的程式碼。
例如官方標準庫中就有許多利用介面程式設計的例子:
由於公司技術棧現在主要由 Go
為主,後續也會繼續更新 Go
相關的實戰經驗;如果你也對學習 Go
感興趣那不妨點個關注吧。