,,繼承的特性子類Go-?Go
2021-08-22
面向?qū)ο蟮木幊田L(fēng)格在開發(fā)者中非常流行,尤其以C++和Java為代表的編程語(yǔ)言風(fēng)靡一時(shí)!
有趣的是,這兩種語(yǔ)言幾乎不出所料都是從C語(yǔ)言衍生而來,但是它們不同于C的面向過程編程。這種面向?qū)ο蟮木幊田L(fēng)格給開發(fā)者帶來了極大的方便,解放了勞動(dòng),松耦合,高內(nèi)聚也成為了設(shè)計(jì)標(biāo)準(zhǔn),讓我們可以更愉快的復(fù)制粘貼,成為代碼的搬運(yùn)工。很多第三方工具都是開箱即用的,語(yǔ)義清晰,職責(zé)明確,都是面向?qū)ο蟮?。編程的好處?/p>
Go 語(yǔ)言也是從 C 語(yǔ)言派生而來的。不知道大家是否也好奇Go語(yǔ)言是否支持面向?qū)ο蟮木幊田L(fēng)格?
準(zhǔn)確地說,Go 支持面向?qū)ο缶幊蹋皇敲嫦驅(qū)ο蟮恼Z(yǔ)言!
不,它和薛定諤的貓一樣不確定嗎?
其實(shí)這個(gè)答案是官方的答案,不是我自己憑空捏造出來的。詳情請(qǐng)參考Is Go an-?
為什么這么說?
Go 支持封裝,但不支持繼承和多態(tài),所以嚴(yán)格按照面向?qū)ο笠?guī)范,Go 語(yǔ)言不是面向?qū)ο蟮木幊陶Z(yǔ)言。
然而,Go 提供的接口是一種非常易于處理且更通用的方式。雖然它在表達(dá)上與其他主流編程語(yǔ)言略有不同,甚至無法實(shí)現(xiàn)多態(tài),但 Go 的接口不僅適用于結(jié)構(gòu)。 Body,它也可以應(yīng)用于任何數(shù)據(jù)類型,這無疑是非常靈活的!
比較有爭(zhēng)議的是繼承。由于沒有關(guān)鍵字支持繼承特性,所以沒有繼承的痕跡。雖然有一些方法可以將類型嵌入到其他類型中來實(shí)現(xiàn)子類化,但這并不是真正的繼承。
因此,Go 既支持面向?qū)ο蟮木幊田L(fēng)格,又不完全是面向?qū)ο蟮木幊陶Z(yǔ)言。
如果換個(gè)角度看問題,正是因?yàn)闆]有繼承,Go比面向?qū)ο蟮木幊陶Z(yǔ)言更輕量級(jí)。您可能希望考慮繼承特性,子類和父類之間的關(guān)系,單繼承或多繼承。訪問控制權(quán)限等問題!
按照面向?qū)ο蟮木幊桃?guī)范,實(shí)現(xiàn)封裝特性的部分應(yīng)該是類和對(duì)象,但是這個(gè)概念和實(shí)現(xiàn)語(yǔ)言的關(guān)鍵詞是分不開的,但是Go沒有關(guān)鍵詞而是C語(yǔ)言 關(guān)鍵字,所以調(diào)用類或者對(duì)象不是很合適,所以下面的解釋過程還是采用這種結(jié)構(gòu)!
如何定義結(jié)構(gòu)
關(guān)鍵字聲明結(jié)構(gòu),屬性之間的回車和換行。
例如下面例子中定義了動(dòng)態(tài)數(shù)組結(jié)構(gòu),下面例子中將使用動(dòng)態(tài)數(shù)組結(jié)構(gòu)作為演示對(duì)象。
type MyDynamicArray struct {
ptr *[]int
len int
cap int
}
在Go語(yǔ)言中定義對(duì)象的多個(gè)屬性時(shí),用直接換行代替分號(hào)來分隔?為什么它與其他主流編程語(yǔ)言不同?
對(duì)于習(xí)慣了分號(hào)結(jié)尾的開發(fā)者來說,他們可能有一段時(shí)間不習(xí)慣 Go 的這種語(yǔ)法,所以他們決定探索 Go 編程規(guī)范!
如果手動(dòng)添加分號(hào),編輯器會(huì)提示分號(hào)重復(fù),所以我猜可能是Go編譯器自動(dòng)添加了分號(hào),用分號(hào)作為語(yǔ)句語(yǔ)句的分隔符。手動(dòng)添加分號(hào)后,Go忽略了或者添加了分號(hào),所以報(bào)了上面的錯(cuò)誤。
這樣做有什么好處?
不是自己加分號(hào),編譯器無條件加分號(hào)的結(jié)果,更何況其他主流編程語(yǔ)言都是手動(dòng)加分號(hào)的!
當(dāng)有多個(gè)屬性時(shí),可以直接換行,不用加分號(hào)作為分隔符。對(duì)于從來沒有接觸過編程語(yǔ)言的小白來說,可能會(huì)省事,但是對(duì)于有編程經(jīng)驗(yàn)的開發(fā)者來說,要記住不能加分號(hào),真的很吵!
如果多個(gè)屬性寫在一行,則沒有換行符。我看你怎么區(qū)分它們。這個(gè)時(shí)候應(yīng)該用逗號(hào)還是分號(hào)隔開?
首先,空格不能分隔多個(gè)屬性,所以試試分號(hào)或逗號(hào)。
根據(jù)提示提示需要分號(hào)或換行符,換行符是標(biāo)準(zhǔn)形式,試試分號(hào)能不能分開?
此時(shí)編輯器不會(huì)報(bào)錯(cuò)或警告,所以一行上的多個(gè)屬性應(yīng)該用分號(hào)隔開,這意味著Go編譯器識(shí)別多個(gè)屬性還是和其他主流編程語(yǔ)言一樣。用數(shù)字隔開,但開發(fā)者不能用!
和上面的規(guī)則類似,記憶很簡(jiǎn)單,驗(yàn)證也比較容易。難點(diǎn)在于理解為什么?
為什么 Go 是這樣設(shè)計(jì)的?或者如何理解這種設(shè)計(jì)思想所代表的語(yǔ)義?
Go 作為一種新的編程語(yǔ)言,不僅體現(xiàn)在具體的語(yǔ)法差異上,更重要的是編程思想的特殊性。
就像面向?qū)ο笾械慕涌诟拍钜粯?,設(shè)計(jì)者只需要定義抽象的行為,并不關(guān)心行為的具體實(shí)現(xiàn)。
如果我們也用這種思維去理解不同的編程語(yǔ)言,那么就可以通過現(xiàn)象看本質(zhì),否則真的很容易陷入語(yǔ)法細(xì)節(jié),進(jìn)而可能會(huì)忽略背后的核心思想。
其實(shí)對(duì)于結(jié)構(gòu)的多屬性分隔符,其實(shí)不管用什么作為分隔符,逗號(hào)或者句號(hào)都可以,只要編譯器能識(shí)別出這是一個(gè)不同的屬性。
因?yàn)榇蠖鄶?shù)主流編程語(yǔ)言一般都是用分號(hào)作為分隔符,開發(fā)者需要手動(dòng)寫分隔符讓編譯器識(shí)別,但是Go語(yǔ)言不這么認(rèn)為,算了,直接換行,我也能識(shí)別出來out(雖然底層的 Go 編譯器在編譯時(shí)仍然使用分號(hào)來表示換行)!
加不加分號(hào),對(duì)于開發(fā)者來說,只是一個(gè)分隔多個(gè)屬性的標(biāo)志。如果不加就可以實(shí)現(xiàn),為什么還要加?
這三個(gè)基本問題是什么、為什么和如何。如果簡(jiǎn)單易學(xué)、易懂,學(xué)什么、怎么學(xué)就夠了,但這樣學(xué)、學(xué)就難免會(huì)出現(xiàn)自治的局面。也就是說,各種編程語(yǔ)言之間沒有關(guān)系,每種語(yǔ)言都是獨(dú)立的?!
世界上有千萬種語(yǔ)言,編程語(yǔ)言也有很多。學(xué)一門新語(yǔ)言不使用舊語(yǔ)言,學(xué)一門新語(yǔ)言和春小白有什么區(qū)別?
學(xué)習(xí)就是學(xué)習(xí),可惜對(duì)舊語(yǔ)言沒有幫助,也沒有加深對(duì)舊語(yǔ)言的理解。這只是對(duì)一種全新語(yǔ)言的純粹學(xué)習(xí)。
語(yǔ)言是由進(jìn)化創(chuàng)造的。它不是空中樓閣。它建立在現(xiàn)有系統(tǒng)的基礎(chǔ)上,逐步發(fā)展和演進(jìn)。任何新語(yǔ)言或多或少都會(huì)找到舊語(yǔ)言的影子。
那何不嘗試一下,弄清楚新語(yǔ)言設(shè)計(jì)的初衷和設(shè)計(jì)過程中面臨的問題,然后再看語(yǔ)言是如何解決問題的。求解的過程稱為實(shí)現(xiàn)細(xì)節(jié)。我覺得這種方式應(yīng)該是更好的學(xué)習(xí)方式!
雖然你不能在語(yǔ)言設(shè)計(jì)環(huán)境中,也不一定了解語(yǔ)言設(shè)計(jì)面臨的挑戰(zhàn),但先問并試著問為什么,你能不能不這樣設(shè)計(jì)等等,應(yīng)該是一個(gè)好的開始。
所以接下來的文章將采用語(yǔ)義分析的角度,嘗試?yán)斫釭o語(yǔ)言背后的原始設(shè)計(jì),并通過大量的輔助測(cè)試來驗(yàn)證猜想。不再是簡(jiǎn)單的知識(shí)羅列過程,當(dāng)然必要的知識(shí)歸納還是很重要的,這個(gè)自然不會(huì)放棄。
既然已經(jīng)定義了動(dòng)態(tài)數(shù)組,也就是設(shè)計(jì)者的工作暫時(shí)告一段落了。作為用戶,我們?nèi)绾问褂梦覀兊膭?dòng)態(tài)數(shù)組?
根據(jù)面向?qū)ο蟮男g(shù)語(yǔ),從類創(chuàng)建對(duì)象的過程稱為實(shí)例化。但是,我們已經(jīng)知道 Go 并不是一個(gè)完整的面向?qū)ο笳Z(yǔ)言,所以為了避免使用面向?qū)ο蟮募夹g(shù)術(shù)語(yǔ)盡可能多地引用 Go 的實(shí)現(xiàn)細(xì)節(jié),我們可以暫時(shí)將其理解為結(jié)構(gòu)類型和結(jié)構(gòu)變量。隨著以后學(xué)習(xí)的深入,我們可能會(huì)對(duì)這部分有更深入的了解。
func TestMyDynamicArray(t *testing.T){
var arr MyDynamicArray
// { 0 0}
t.Log(arr)
}
以上寫法沒有特別強(qiáng)調(diào)。它完全是使用之前文章中介紹過的語(yǔ)法規(guī)則來實(shí)現(xiàn)的。 var arr 表示聲明類型的變量 arr。此時(shí)直接打印變量的值,結(jié)果為{0 0}。
最后兩個(gè)值
都是0,自然容易理解,因?yàn)槲覀冊(cè)贕o語(yǔ)言中解釋變量的時(shí)候已經(jīng)介紹過了。 Go的變量類型默認(rèn)初始化有一個(gè)對(duì)應(yīng)的0值,而int類型的len cap屬性自然是0,而ptr *[]int是數(shù)組的指針,所以是nil。
等等,有些不對(duì)勁。這里有一個(gè)設(shè)計(jì)錯(cuò)誤。明明叫做動(dòng)態(tài)數(shù)組,里面的結(jié)果是切片的。怎么回事?
先修復(fù)這個(gè)錯(cuò)誤??梢钥闯觯中拇笠獾男Ч愀饬?,語(yǔ)義發(fā)生了變化。我先糾正一下!
我們知道要使用數(shù)組,必須指定數(shù)組的初始化長(zhǎng)度。第一感覺是用cap所代表的容量來初始化*[cap]int數(shù)組,但是不行。編輯器提示必須使用整數(shù)。
雖然 cap 是一個(gè) int 類型的變量,但內(nèi)部數(shù)組 [cap]int 不識(shí)別這個(gè)方法。可能是因?yàn)檫@兩個(gè)變量是一起聲明的。 cap 和 (cap)int 都是變量,不能賦值。
那么如果指定了初始化長(zhǎng)度,應(yīng)該指定多少,如果是0,語(yǔ)義上是正確但與實(shí)際使用不符,因?yàn)檫@樣的話,內(nèi)部數(shù)組就不能按照方法插入了!
所以數(shù)組的初始化長(zhǎng)度不能為零,解決了無法操作數(shù)組的問題,但是語(yǔ)義不正確。因此,在這種情況下,需要維護(hù)len和cap這兩個(gè)變量的值,以確保語(yǔ)義和邏輯正確。 ,其中l(wèi)en代表數(shù)組的實(shí)際數(shù)量,cap代表內(nèi)部數(shù)組的實(shí)際分配長(zhǎng)度。由于這兩個(gè)變量非常重要,不應(yīng)被調(diào)用者隨意修改,最多只能查看變量的值,所以必須提供一種機(jī)制來保護(hù)變量的值。
接下來我們嘗試用函數(shù)封裝的思路來完成這個(gè)需求,代碼實(shí)現(xiàn)如下:
type MyDynamicArray struct {
ptr *[10]int
len int
cap int
}
func TestMyDynamicArray(t *testing.T){
var myDynamicArray MyDynamicArray
t.Log(myDynamicArray)
myDynamicArray.len = 0
myDynamicArray.cap = 10
var arr [10]int
myDynamicArray.ptr = &arr
t.Log(myDynamicArray)
t.Log(*myDynamicArray.ptr)
}
var聲明結(jié)構(gòu)體變量并設(shè)置結(jié)構(gòu)體的基本屬性,然后操作內(nèi)部數(shù)組實(shí)現(xiàn)對(duì)數(shù)組的訪問修改。
然而,我們犯了一個(gè)典型的錯(cuò)誤。調(diào)用者不應(yīng)該關(guān)注實(shí)現(xiàn)細(xì)節(jié)。這不是打包要做的!
具體的實(shí)現(xiàn)細(xì)節(jié)由設(shè)計(jì)者完成面向?qū)ο缶幊陶Z(yǔ)言,并將相關(guān)數(shù)據(jù)封裝成一個(gè)整體,對(duì)外提供相應(yīng)的接口,讓調(diào)用者可以安全方便地調(diào)用。
第一步是封裝內(nèi)部數(shù)組相關(guān)的兩個(gè)變量,只對(duì)外提供訪問接口,不提供設(shè)置接口,防止調(diào)用者隨意修改。
顯然這部分應(yīng)該由函數(shù)來實(shí)現(xiàn),所以有如下轉(zhuǎn)換過程。
可惜編輯器直接報(bào)錯(cuò):它必須是類型名或指向類型名的指針。
函數(shù)不能放置在結(jié)構(gòu)中。這與 C 系列非常相似,但是像 Java 這樣的衍生系列會(huì)覺得不可思議。無論如何,這意味著結(jié)構(gòu)只能定義結(jié)構(gòu)而不能定義行為!
那我們把函數(shù)移到結(jié)構(gòu)外,但是我們定義的函數(shù)名字叫l(wèi)en,而且系統(tǒng)也有l(wèi)en函數(shù),這時(shí)候能正常運(yùn)行嗎?讓我們拭目以待,眼見為實(shí)。
除了函數(shù)本身報(bào)錯(cuò),函數(shù)內(nèi)部的len也報(bào)錯(cuò),因?yàn)榇藭r(shí)函數(shù)和結(jié)構(gòu)體還沒有建立任何連接。如何訪問 len 屬性?不報(bào)錯(cuò)才怪!
解決這個(gè)問題很簡(jiǎn)單。直接將結(jié)構(gòu)體的指針傳遞給len函數(shù)是不夠的,這樣在函數(shù)內(nèi)部可以訪問結(jié)構(gòu)體的屬性。
從設(shè)計(jì)的角度來看,它確實(shí)解決了函數(shù)定義的問題,但是用戶調(diào)用函數(shù)的方式看起來與面向?qū)ο蟮木帉懛绞接行┎煌?/p>
func TestMyDynamicArray(t *testing.T) {
var myDynamicArray MyDynamicArray
t.Log(myDynamicArray)
myDynamicArray.len = 0
myDynamicArray.cap = 10
var arr [10]int
myDynamicArray.ptr = &arr
t.Log(myDynamicArray)
t.Log(*myDynamicArray.ptr)
(*myDynamicArray.ptr)[0] = 1
t.Log(*myDynamicArray.ptr)
t.Log(len(&myDynamicArray))
}
面向?qū)ο蟮姆椒ㄒ话愣际峭ㄟ^點(diǎn)操作符來實(shí)現(xiàn)的。訪問屬性或方法,以及我們實(shí)現(xiàn)的屬性訪問。但是方法是函數(shù)調(diào)用的典型形式嗎?這看起來不像是一種方法!
為了讓普通函數(shù)看起來像面向?qū)ο蟮姆椒?,Go做了如下改動(dòng),將當(dāng)前結(jié)構(gòu)體的變量聲明移到函數(shù)名的前面,從而實(shí)現(xiàn)類似于this或self in 面向語(yǔ)言的效果。
func len(myArr *MyDynamicArray) int {
return myArr.len
}
這時(shí)候方法名和參數(shù)返回值又報(bào)錯(cuò)了。根據(jù)提示,函數(shù)名和字段名不能相同?
這真的是一件很神奇的事情。有沒有可能 Go 無法區(qū)分函數(shù)和字段?這是未知的。
然后我們要修改函數(shù)名,改成面向?qū)ο笾辛餍械姆椒?guī)則,如下:
func (myArr *MyDynamicArray) GetLen() int {
return myArr.len
}
讓我們簡(jiǎn)單地談?wù)?Go 的可訪問性規(guī)則。大寫字母開頭表示公共權(quán)限,小寫字母開頭表示私有權(quán)限。 Go 只有這兩種類型的權(quán)限,這兩種權(quán)限都是特定于包的。先這樣理解就好了。
根據(jù)實(shí)驗(yàn)中得到的方法規(guī)則,繼續(xù)改進(jìn)其他方法,補(bǔ)充其他方法。
現(xiàn)在我們已經(jīng)解決了私有變量的可訪問性問題。初始化邏輯沒有處理。一般來說,初始化邏輯可以在構(gòu)造函數(shù)中執(zhí)行。 Go 是否支持構(gòu)造函數(shù)以及如何觸發(fā)構(gòu)造函數(shù)?功能?
嘗試按照其他主流編程語(yǔ)言中構(gòu)造函數(shù)的編寫方式來編寫Go的構(gòu)造函數(shù)。沒想到Go編譯器直接報(bào)錯(cuò),提示重新定義了類型,影響了其余部分!
如果修改方法名,理論上可以解決報(bào)錯(cuò)問題,但這不是構(gòu)造函數(shù)的樣子。 Go 可能不支持構(gòu)造函數(shù)嗎?
此時(shí)構(gòu)造函數(shù)的面向?qū)ο笮问睫D(zhuǎn)化為自定義函數(shù)實(shí)現(xiàn)的構(gòu)造函數(shù)。更準(zhǔn)確的說,這是一個(gè)類似于工廠模式實(shí)現(xiàn)的構(gòu)造函數(shù)方法。
func NewMyDynamicArray() *MyDynamicArray {
var myDynamicArray MyDynamicArray
return &myDynamicArray
}
Go 語(yǔ)言真的不支持構(gòu)造函數(shù)嗎?
至于是否支持構(gòu)造函數(shù),或者應(yīng)該如何支持,真相不明。隨著學(xué)習(xí)的深入,相信以后會(huì)有明確的答案。以下是我個(gè)人觀點(diǎn)的簡(jiǎn)要表達(dá)。
首先我們知道Go的結(jié)構(gòu)體只能定義數(shù)據(jù),結(jié)構(gòu)體的方法必須定義在結(jié)構(gòu)體之外。為了符合面向?qū)ο蟮氖褂昧?xí)慣,即通過實(shí)例對(duì)象的點(diǎn)操作符來訪問方法。 Go的方法只能是函數(shù)的變體,即普通函數(shù)指向結(jié)構(gòu)體變量的聲明部分,移到函數(shù)名前面來實(shí)現(xiàn)方法。這種把函數(shù)變成方法的模式也符合Go一貫的命名規(guī)則:按照人的思維習(xí)慣命名,先有輸入再輸出等邏輯。
結(jié)構(gòu)方法從語(yǔ)法和語(yǔ)義兩個(gè)維度支持面向?qū)ο笠?guī)范,那么構(gòu)造函數(shù)應(yīng)該怎么做才能實(shí)現(xiàn)面向?qū)ο螅?/p>
顧名思義,構(gòu)造函數(shù)應(yīng)該是一個(gè)函數(shù),而不是一個(gè)方法。該方法由指向自身的參數(shù)組成。這一點(diǎn)不應(yīng)包含在構(gòu)造函數(shù)中。否則,應(yīng)該有對(duì)象的實(shí)例,并且會(huì)構(gòu)造紗線?
既然構(gòu)造函數(shù)是普通函數(shù),那么按照面向?qū)ο蟮拿s定,方法名應(yīng)該是結(jié)構(gòu)體名,但是如果你真的操作了,編輯器會(huì)直接報(bào)錯(cuò),所以這不符合到面向?qū)ο蟮拿s定!
這樣構(gòu)造函數(shù)的名字可能不是結(jié)構(gòu)類型的名字,而是其他特殊的名字。最好能通過名字知道名字,并且有在實(shí)例化對(duì)象時(shí)自動(dòng)調(diào)用的能力。
當(dāng)然,這個(gè)名字取決于 Go 的設(shè)計(jì)者如何命名。在這里靠猜測(cè)很難猜到,否則我就是設(shè)計(jì)師!
另外,還有一種可能,就是Go沒有構(gòu)造函數(shù)。如果要實(shí)現(xiàn)構(gòu)造函數(shù)的邏輯,只能另尋他路了。
有什么可靠的依據(jù)嗎?
我認(rèn)為這是可能的。構(gòu)造函數(shù)雖然提供了自動(dòng)初始化的能力,但如果真的在構(gòu)造函數(shù)中加入復(fù)雜的初始化邏輯,無疑會(huì)增加日后排查的難度,帶來一定的用戶。閱讀障礙,所以在某種程度上,構(gòu)造函數(shù)很可能被濫用!
這是否意味著不需要構(gòu)造函數(shù)?
不能說同樣的話。除了基本的變量初始化和簡(jiǎn)單的邏輯之外,構(gòu)造函數(shù)在實(shí)際編程中還有一定的用途。為了避免濫用,直接禁用。有點(diǎn)像喝毒解渴的感覺吧?
因此,我個(gè)人的觀點(diǎn)是,構(gòu)造函數(shù)的初始化邏輯應(yīng)該保留,或者可以用其他方式實(shí)現(xiàn),或者干脆放棄構(gòu)造函數(shù),讓編譯器自動(dòng)實(shí)現(xiàn)構(gòu)造函數(shù),就像編譯器可以自動(dòng)添加一樣就像多個(gè)字段之間的分號(hào)。
如果開發(fā)者真的需要構(gòu)造函數(shù),結(jié)構(gòu)體初始化的邏輯總是可以通過工廠模式或者單例模式自定義,所以放棄也可以!
最后,以上純屬個(gè)人猜想。不知道Go中有沒有構(gòu)造函數(shù)。如果你知道,請(qǐng)清楚地告訴我答案。我個(gè)人傾向于沒有構(gòu)造函數(shù),最多只提供類似的構(gòu)造函數(shù)初始化。邏輯!
現(xiàn)在,我們已經(jīng)封裝了結(jié)構(gòu)體的數(shù)據(jù),定義了結(jié)構(gòu)體的方法,實(shí)現(xiàn)了結(jié)構(gòu)體的工廠函數(shù)。那么讓我們繼續(xù)完善動(dòng)態(tài)數(shù)組,實(shí)現(xiàn)數(shù)組的基本操作。
func NewMyDynamicArray() *MyDynamicArray {
var myDynamicArray MyDynamicArray
myDynamicArray.len = 0
myDynamicArray.cap = 10
var arr [10]int
myDynamicArray.ptr = &arr
return &myDynamicArray
}
func TestMyDynamicArray(t *testing.T) {
myDynamicArray := NewMyDynamicArray()
t.Log(myDynamicArray)
}
首先將測(cè)試用例中的邏輯提取到工廠函數(shù)中。不帶參數(shù)的工廠函數(shù)初始化的默認(rèn)內(nèi)部數(shù)組長(zhǎng)度為10,然后再考慮調(diào)用者的規(guī)范和動(dòng)態(tài)數(shù)組函數(shù)的實(shí)現(xiàn),暫時(shí)實(shí)現(xiàn)最基本的功能。 .
初始化的內(nèi)部數(shù)組都是零值,所以需要先提供外界可以添加的接口,實(shí)現(xiàn)如下:
func (myArr *MyDynamicArray) Add(index, value int) {
if myArr.len == myArr.cap {
return
}
if index < 0 || index > myArr.len {
return
}
for i := myArr.len - 1; i >= index; i-- {
(*myArr.ptr)[i+1] = (*myArr.ptr)[i]
}
(*myArr.ptr)[index] = value
myArr.len++
}
由于默認(rèn)的初始化工廠函數(shù)暫時(shí)是一個(gè)定長(zhǎng)數(shù)組,所以新元素實(shí)際上是一個(gè)定長(zhǎng)數(shù)組,但這并不妨礙動(dòng)態(tài)數(shù)組部分的后續(xù)實(shí)現(xiàn)。
為了方便操作,提供了插入頭部和插入尾部?jī)蓚€(gè)接口,可以實(shí)現(xiàn)更高級(jí)的基于動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu)。
func (myArr *MyDynamicArray) AddLast(value int) {
myArr.Add(myArr.len, value)
}
func (myArr *MyDynamicArray) AddFirst(value int) {
myArr.Add(0, value)
}
為了測(cè)試動(dòng)態(tài)數(shù)組的算法是否正確,提供了打印方法查看數(shù)組的結(jié)構(gòu)。
可以看出打印方式顯示的數(shù)據(jù)結(jié)構(gòu)和真實(shí)的結(jié)構(gòu)數(shù)據(jù)是一樣的,接下來我們更有信心繼續(xù)封裝動(dòng)態(tài)數(shù)組!
func (myArr *MyDynamicArray) Set(index, value int) {
if index < 0 || index >= myArr.len {
return
}
(*myArr.ptr)[index] = value
}
func (myArr *MyDynamicArray) Get(index int) int {
if index < 0 || index >= myArr.len {
return -1
}
return (*myArr.ptr)[index]
}
這兩個(gè)接口比較簡(jiǎn)單,更新數(shù)組指定索引的元素,根據(jù)索引查詢數(shù)組的值。
接下來,我們開始測(cè)試動(dòng)態(tài)數(shù)組的所有接口!
動(dòng)態(tài)數(shù)組暫時(shí)告一段落。不知道大家有沒有好奇我們?yōu)槭裁从脛?dòng)態(tài)數(shù)組作為例子來解釋面向?qū)ο螅?/p>
其實(shí)主要是驗(yàn)證上一篇的猜想,即切片和數(shù)組是什么關(guān)系?
我認(rèn)為切片的底層是一個(gè)數(shù)組,但是語(yǔ)法層面提供了支持,讓你看不到數(shù)組的陰影。既然仙女已經(jīng)學(xué)會(huì)了面向?qū)ο?,那么就用面向?qū)ο蟮姆绞絹韺?shí)現(xiàn)切片的功能,雖然不能模擬語(yǔ)法層面的實(shí)現(xiàn),但是功能特性是可以模仿的!
以下是對(duì)本文知識(shí)點(diǎn)的總結(jié),即封裝的實(shí)現(xiàn)。
如何封裝結(jié)構(gòu)
之所以叫結(jié)構(gòu)體,是因?yàn)镚o的關(guān)鍵字不是,而且也是面向?qū)ο缶幊田L(fēng)格中唯一支持的特性。不支持繼承和多態(tài),我會(huì)開一篇文章詳細(xì)說明。
結(jié)構(gòu)是封裝數(shù)據(jù)的一種手段。結(jié)構(gòu)體只能定義數(shù)據(jù),不能定義方法。這些數(shù)據(jù)有時(shí)稱為字段,有時(shí)稱為屬性或簡(jiǎn)稱為變量。至于叫什么,也沒什么特別的。重要的是,如何命名與環(huán)境的語(yǔ)義有關(guān)。
type MyDynamicArray struct {
ptr *[10]int
len int
cap int
}
這個(gè)結(jié)構(gòu)中有三個(gè)變量。變量由換行符而不是分號(hào)和換行符分隔。一開始感覺有點(diǎn)奇怪,不過編輯器一般都很聰明。如果你習(xí)慣性地加分號(hào),會(huì)提示你刪除,所以不用在意語(yǔ)法細(xì)節(jié)。
結(jié)構(gòu)不支持寫函數(shù),只支持?jǐn)?shù)據(jù)結(jié)構(gòu),也就是說數(shù)據(jù)和行為是分離的,兩者的關(guān)系比較弱。
func (myArr *MyDynamicArray) IsEmpty() bool {
return myArr.len == 0
}
這種方式的功能與普通功能略有不同。包含結(jié)構(gòu)變量的參數(shù)被推進(jìn)到函數(shù)名的前面。語(yǔ)義也很清楚。它是指結(jié)構(gòu)的功能。為了區(qū)別于普通函數(shù),這種函數(shù)被稱為方法。
其實(shí)就簡(jiǎn)單的實(shí)現(xiàn)函數(shù)而言,方法和函數(shù)沒有區(qū)別,無非就是調(diào)用者的使用方式!
func IsEmpty(myArr *MyDynamicArray) bool {
return myArr.len == 0
}
之所以采用這種設(shè)計(jì)方式,一方面是體現(xiàn)了函數(shù)的重要性,畢竟在Go語(yǔ)言中它們是一等公民!
另一方面是為了實(shí)現(xiàn)面向?qū)ο蟮恼Z(yǔ)法習(xí)慣,不管是屬性還是方法面向?qū)ο缶幊陶Z(yǔ)言,都用點(diǎn)號(hào)調(diào)用。操作員。
在官方文檔中,這個(gè)結(jié)構(gòu)參數(shù)被稱為接收者,因?yàn)閿?shù)據(jù)和行為是弱相關(guān)的。發(fā)送數(shù)據(jù)的人是誰(shuí)?
不言而喻,發(fā)送方應(yīng)該是調(diào)用方傳遞過來的結(jié)構(gòu)體實(shí)例對(duì)象,結(jié)構(gòu)體變量將數(shù)據(jù)結(jié)構(gòu)體發(fā)送給接收方方法,從而將數(shù)據(jù)和行為聯(lián)系在一起。
func TestMyDynamicArray(t *testing.T) {
myDynamicArray := NewMyDynamicArray()
fmt.Println(myDynamicArray.IsEmpty())
}
好的,以上就是第一次面向?qū)ο篌w驗(yàn)的所有部分。這只是很小的一部分,我花了三天時(shí)間。我想說的是,轉(zhuǎn)變思維不容易,寫好文章也不容易。 !
在下一篇文章中,我會(huì)繼續(xù)介紹面向?qū)ο蟮陌b特性,講解更多干貨。如果您覺得本文對(duì)您有幫助,請(qǐng)轉(zhuǎn)發(fā)您的評(píng)論,感受您的閱讀!