
前言
我的讀者中應(yīng)該大部分都是 Java 從業(yè)者,不知道寫 Java 這些年是否真的有找到對(duì)象?
沒找到也沒關(guān)系,總不能在一棵樹上吊死,我們也可以來 Go 這邊看看,說不定會(huì)有新發(fā)現(xiàn)。
開個(gè)玩笑,本文會(huì)以一個(gè) Javaer 的角度來聊聊 Go 語言中的面向?qū)ο蟆?/p>
OOP
面向?qū)ο筮@一詞來源于Object Oriented Programming,也就是大家常說的 OOP。
對(duì)于 Go 是否為面向?qū)ο蟮木幊陶Z言,這點(diǎn)也是討論已久;不過我們可以先看看官方的說法:

其他的我們暫且不看,Yes and No. 這個(gè)回答就比較微妙了,為了這篇文章還能寫下去我們先認(rèn)為 Go 是面向?qū)ο蟮摹?/p>
面向?qū)ο笥兄齻€(gè)重要特征:
- 封裝
- 繼承
- 多態(tài)
封裝
Go 并沒有 Class 的概念,卻可以使用 struct 來達(dá)到類似的效果,比如我們可以對(duì)汽車聲明如下:
type Car struct {
Name string
Price float32
}
與 Java 不同的是,struct 中只存儲(chǔ)數(shù)據(jù),不能定義行為,也就是方法。
當(dāng)然也能為 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 參數(shù)可以理解為 Java 中的 this 以及 Python 中的 self,就語義來說我覺得 go 更加簡單一些。
畢竟我見過不少剛學(xué)習(xí) Java 的萌新非常不理解 this 的含義與用法。
匿名結(jié)構(gòu)體
既然談到結(jié)構(gòu)體了那就不得不聊聊 Go 支持的匿名結(jié)構(gòu)體(雖然和面向?qū)ο鬀]有太大關(guān)系)
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 允許我們?cè)诜椒▋?nèi)部創(chuàng)建一個(gè)匿名的結(jié)構(gòu)體,后續(xù)還能直接使用該結(jié)構(gòu)體來獲取數(shù)據(jù)。
這點(diǎn)在我們調(diào)用外部接口解析響應(yīng)數(shù)據(jù)時(shí)非常有用,創(chuàng)建一個(gè)臨時(shí)的結(jié)構(gòu)體也不用額為維護(hù);同時(shí)還能用面向?qū)ο蟮姆绞将@取數(shù)據(jù)。
相比于將數(shù)據(jù)存放在 map 中用字段名獲取要優(yōu)雅許多。
繼承
Go 語言中并沒有 Java、C++ 這樣的繼承概念,類之間的關(guān)系更加扁平簡潔。
各位 Javaer 應(yīng)該都看過這類圖:

相信大部分新手看到這圖時(shí)就已經(jīng)懵逼,更別說研究各個(gè)類之間的關(guān)系了。
不過這樣好處也明顯:如果我們抽象合理,整個(gè)系統(tǒng)結(jié)構(gòu)會(huì)很好維護(hù)和擴(kuò)展;但前提是我們能抽象合理。
在 Go 語言中更推薦使用組合的方式來復(fù)用數(shù)據(jù):
type ElectricCar struct {
Car
Battery int32
}
func main() {
xp := ElectricCar{
Car{Name: "xp", Price: 200},
70,
}
fmt.Println(xp.Name)
}
這樣我們便可以將公共部分的數(shù)據(jù)組合到新的 struct 中,并能夠直接使用。
接口(多態(tài))
面向接口編程的好處這里就不在贅述了,我們來看看 Go 是如何實(shí)現(xiàn)的:
type ElectricCar struct {
Car
Battery int32
}
type PetrolCar struct {
Car
Gasoline int32
}
//定義一個(gè)接口
type RunService interface {
Run()
}
// 實(shí)現(xiàn)1
func (car *PetrolCar) Run() {
fmt.Printf("%s PetrolCar run \n", car.Name)
}
// 實(shí)現(xiàn)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)
}
首先定義了一個(gè)接口 RunService;ElectricCar 與 PetrolCar 都實(shí)現(xiàn)了該接口。
可以看到 Go 實(shí)現(xiàn)一個(gè)接口的方式并不是 implement,而是用結(jié)構(gòu)體聲明一個(gè)相同簽名的方法。
這種實(shí)現(xiàn)模式被稱為”鴨子類型“,Python 中的接口也是類似的鴨子類型。

詳細(xì)介紹可以參考這篇:Python 中的面向接口編程
接口當(dāng)然也是可以擴(kuò)展的,類似于 struct 中的嵌套:
type DiService interface {
Di()
}
//定義一個(gè)接口
type RunService interface {
DiService
Run()
}

得益于 Go 的強(qiáng)類型,剛才的 struct 也得實(shí)現(xiàn) DiService 這個(gè)接口才能編譯通過。
總結(jié)
到這里應(yīng)該是能理解官方所說的 Yes and No. 的含義了;Go 對(duì)面向?qū)ο蟮恼Z法不像 Java 那么嚴(yán)苛,甚至整個(gè)語言中都找不到 object(對(duì)象) 這個(gè)關(guān)鍵詞;但是利用 Go 里的其他特性也是能實(shí)現(xiàn) OOP 的。
是否為面向?qū)ο笪矣X得并不重要,主要目的是我們能寫出易擴(kuò)展好維護(hù)的代碼。
例如官方標(biāo)準(zhǔn)庫中就有許多利用接口編程的例子:

由于公司技術(shù)?,F(xiàn)在主要由 Go 為主,后續(xù)也會(huì)繼續(xù)更新 Go 相關(guān)的實(shí)戰(zhàn)經(jīng)驗(yàn);如果你也對(duì)學(xué)習(xí) Go 感興趣那不妨點(diǎn)個(gè)關(guān)注吧。