【Go語言入門系列】(八)Go語言是不是面嚮物件語言?

行人觀學發表於2020-09-01

【Go語言入門系列】前面的文章:

1. Go是物件導向的語言嗎?

【Go語言入門系列】(七)如何使用Go的方法?這一文中已經介紹了方法的概念,但這個方法實際上並不是物件導向中的方法。方法實際上是使用者給其定義的型別的增加的新行為,實際上也是個函式。

關於這個問題,官方文件中有回答:

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

既是也不是。儘管Go擁有型別和方法,也允許物件導向風格的程式設計,但它沒有型別層級。 在Go中“介面”的概念提供了不同的方法,我們相信它易於使用且在某些方面更通用。 也有一些在其它型別中嵌入型別的方法,來提供類似(而非完全相同)的東西進行子類化。 此外,Go中的方法比C++或Java中的更通用:它們可被定義為任何種類的資料。 甚至是像普通的“未裝箱”整數這樣的內建型別。它們並不受結構(類)的限制。

此外,型別層級的缺失也使Go中的“物件”感覺起來比C++或Java的更輕量級

有了這個回答,下面介紹的“繼承”和“重寫”的概念並不是嚴格的物件導向中的繼承和重寫的概念。這裡只借用這兩個名詞來表示Go的兩種特性。

2.“繼承”

在物件導向中,繼承是子類和父類之間的關係,子類會繼承父類的公有成員變數和成員方法。

前面提到過,Go允許物件導向風格的程式設計。那Go如何“繼承”呢?

2.1.“繼承”欄位

【Go語言入門系列】(五)指標和結構體的使用這一文中,介紹了匿名欄位(也叫嵌入欄位):

package main

import "fmt"

type people struct {
	name string
	age int
}

type student struct {
	people
	school string
}

func (s student) say() {
	fmt.Printf("我是%s,今年%d歲了,在%s上學。", s.name, s.age, s.school)
}

func main() {
	stu := student{people{"行小觀", 1}, "陽光小學"}
	stu.say()
}

執行:

我是行小觀,今年1歲了,在陽光小學上學

結構體student中有匿名欄位people,所以student就有了peoplenameage欄位。當匿名欄位是一個結構體時,那麼該結構體的全部欄位都會被引入當前的結構體中。這是不是很像物件導向中的繼承?子類繼承父類的公有成員變數。

考慮下面一個問題,如果studentpeople中都有name欄位,那麼訪問時,Go語言是如何處理的?

package main

import "fmt"

type people struct {
	name string //人名
	age int
}

type student struct {
	people
	name string //學生名
	school string
}

func main() {
	stu := student{people{"李二狗", 1}, "李向前", "陽光學校"}
	fmt.Println(stu.name) //李向前
	fmt.Println(stu.people.name) //李二狗
}

此時就出現了欄位衝突,Go會先訪問外層的欄位。比如,stu.name李向前(外層),stu.people.name李二狗(內層)。

2.2.“繼承”方法

我們通過接收者把函式繫結到結構體型別上,這樣的函式稱為方法,方法就在概念上屬於了接收者對應的結構體。

上面通過結構體中匿名欄位實現了“繼承”欄位的效果,對於方法來說,同樣可以。下面是一個例項:

package main

import "fmt"

type people struct {
	name string
	age int
}

type student struct {
	people
	school string
}

type programmer struct {
	people
	language string
}

func (p people) say() {
	fmt.Printf("我是%s,今年%d歲了,和我一起學習Go語言吧!\n", p.name, p.age)
}

func main() {
	stu := student{people{"行小觀", 1}, "陽光小學"}
	stu.say()
	prom := programmer{people{"張三", 1}, "藍天建築有限公司"}
	prom.say()
}

執行:

我是行小觀,今年1歲了,和我一起學習Go語言吧!
我是張三,今年1歲了,和我一起學習Go語言吧!

say()方法的接收者是people型別,而結構體studentprogrammer中都有匿名欄位people,所以stuprom都能呼叫say()方法。這是不是很像面嚮物件語言中子類繼承父類的公有方法?

3. “重寫”

3.1. “重寫”欄位

前面已經介紹瞭如果結構體和作為其欄位的結構體的欄位衝突瞭如何處理。有了這個特性,我們就可以“重寫”欄位。

package main

import "fmt"

type people struct {
	name string //乳名
	age int
}

type student struct {
	people
	name string //大名
	school string
}

func (s student) say() {
	fmt.Printf("我是%s,今年%d歲了,和我一起學習Go語言吧!\n", s.name, s.age)
}

func main() {
	stu := student{people{"李二狗", 1}, "李向前","陽光小學"}
	stu.say()
}

執行:

我是李向前,今年1歲了,和我一起學習Go語言吧!

李二狗是乳名,李向前才是大名。自我介紹時說的是大名李向前。如果需要乳名,則使用stu.people.name

3.2.“重寫”方法

下面是一個例項:

package main

import "fmt"

type people struct {
	name string
	age int
}

type student struct {
	people
	school string
}

type programmer struct {
	people
	language string
}

func (p people) say() { //people的say方法
	fmt.Printf("我是%s,今年%d歲了,和我一起學習Go語言吧!\n", p.name, p.age)
}

func (s student) say() { //student重寫people的say方法
	fmt.Printf("我是%s,是個學生,今年%d歲了,我在%s上學!\n", s.name, s.age, s.school)
}

func (p programmer) say() { //programmer重寫people的say方法
	fmt.Printf("我是%s,是個程式設計師,今年%d歲了,我使用%s語言!\n", p.name, p.age, p.language)
}

func main() {
	stu := student{people{"李向前", 1}, "陽光小學"}
	stu.say()
	prmger := programmer{people{"張三", 1}, "Go"}
	prmger.say()
}

執行:

我是李向前,是個學生,今年1歲了,我在陽光小學上學!
我是張三,是個程式設計師,今年1歲了,我使用Go語言!

studentprogrammer“繼承”了peoplesay()方法,但是不合適,於是各自“重寫”了say()方法。

看到這裡,你就理解了官方文件中的那兩句話是什麼意思了。

“儘管Go擁有型別和方法,也允許物件導向風格的程式設計,但它沒有型別層級”

“型別層級的缺失也使Go中的“物件”感覺起來比C++或Java的更輕量級”

作者簡介

我是行小觀,我會在公眾號『行人觀學』中持續更新Java、Go、資料結構和演算法、計算機基礎等相關文章。


本文章已收錄進系列文章「Go語言入門系列」,本系列從Go語言基礎開始介紹,適合從零開始的初學者。


歡迎關注,我們一起踏上程式設計的行程。

如有錯誤,還請指正。

相關文章