Go 去找個對象吧

前言

我的讀者中應該大部分都是 Java 從業者,不知道寫 Java 這些年是否真的有找到對象?

沒找到也沒關係,總不能在一棵樹上吊死,我們也可以來 Go 這邊看看,說不定會有新發現。

開個玩笑,本文會以一個 Javaer 的角度來聊聊 Go 語言中的面向對象。

OOP

面向對象這一詞來源於Object Oriented Programming,也就是大家常說的 OOP

對於 Go 是否爲面向對象的編程語言,這點也是討論已久;不過我們可以先看看官方的說法:

其他的我們暫且不看,Yes and No. 這個回答就比較微妙了,爲了這篇文章還能寫下去我們先認爲 Go 是面向對象的。


面向對象有着三個重要特徵:

  1. 封裝
  2. 繼承
  3. 多態

封裝

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 語言中並沒有 JavaC++ 這樣的繼承概念,類之間的關係更加扁平簡潔。

各位 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)

}

首先定義了一個接口 RunServiceElectricCarPetrolCar 都實現了該接口。

可以看到 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 感興趣那不妨點個關注吧。

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