結構模式通過常用的結構和關係幫我們塑造應用,在go語言中最常用的模式是復合模式,因為在go語言中沒有繼承的概念,而鼓勵復合的使用
復合設計模式傾向於組合(通常定義為具有關係)而不是繼承(即關係)。自90年代以來,組合繼承方法一直是工程師們討論的話題。我們將學習如何使用has a
方法建立物件結構。總之,go沒有繼承權,因為它不需要繼承權!
在復合設計模式中,您將建立物件的層次結構和樹。物件有不同的物件,其中有自己的字段和方法。這種方法非常強大,解決了許多繼承和多重繼承的問題。例如,乙個典型的繼承問題是當乙個實體從兩個完全不同的類繼承時,這兩個類之間完全沒有關係。想象一下訓練的運動員和游泳的游泳運動員。
swimmer
類繼承自athlete
,因此也繼承了train
方法並且宣告了自己的swim
方法。同時也可以有乙個自行車運動員cyclist
宣告乙個ride
方法。但是如果現在有乙個會吃東西的動物,像狗會叫一樣:
你也可以有乙個魚,當然可以游泳,但是如何解決呢?魚不可以像運動員一樣訓練。你可以建立乙個運動員介面,擁有乙個swim方法,讓游泳運動員和魚實現它。這應該是最好的實現了,但你必須實現swim方法兩次,**重用性則會受到影響。如果是鐵人三項呢?運動員需要游泳跑步和騎車。多重的介面實現可以暫時解決這個問題,但很快就沒法維護了。
復合模式就是為了避免這種會導致應用臃腫、**不清潔的層級地獄。我們可以使用go實現兩種復合模式:直接復合direct composition和嵌入復合embedding composition。
復合模式是一種純純結構模式,對於結構本身沒什麼可測試的。因此這次不會寫單元測試,在此簡單的描述復合模式的建立。
首先從athlete
結構和train()
方法開始
type athlete struct{}
func (a *athlete) train()
上述**很直接,train()
方法列印字串,現在再建立乙個游泳運動員a,將athlete
組合在裡面。
type compositeswimmera struct
組合型別compositeswimmera
有乙個athlete
型別的資料域myathlete
,同時還有乙個func()
。在go中,函式是「一等公民」,他們可以像任何變數一樣用作引數、字段。因此compositeswimmera
有乙個myswim
字段,該欄位儲存乙個閉包,不接受任何引數也不返回任何內容。我們怎麼才能為他分配函式呢?我們建立乙個與func()
型別匹配的函式(沒有引數, 沒有返回值)。
func swim()
打完收工。swim()
方法沒有引數沒有返回值,因此可以用在compositeswimmera
結構體的myswim
欄位上:
swimmer := compositeswimmera
swimmer.myathlete.train()
swimmer.myswim()
現在有乙個叫swim()
的函式,我們將該函式分配到myswim
欄位上。注意,swim
型別沒有將執行其內容的括號。這樣我們把整個函式複製到myswim
方法。此時我們沒有傳遞任何運動員到myathlete
字段但是已經可以使用使用了。
$ go run main.go
training
swimming!
其實這是由於go語言的自動初始化。如果你不給compositeswimmera
傳遞乙個athlete
結構體,編譯器會為他自動建立乙個,並賦欄位初值為0。再看看上面對compositeswimmera
的定義,再來解決魚的問題就不難了。首先我們建立乙個animal
結構體:
type animal struct{}
func (a *animal) eat ()
再建立乙個shark
物件嵌入乙個animal
物件:
type shark struct
等下,animal
型別欄位的名字去哪了?還記得嵌入這個詞嗎?在go裡,你可以把物件嵌入物件裡,看起來就像是繼承。也就是說,我們不必顯式呼叫欄位名來訪問其字段和方法,因為它們將是我們的一部分。這樣說的話下面的**就很ok了:
fish := shark
fish.eat()
fish.swim()
現在我們有了乙個有初始值的、嵌入的animal
型別。這就是為什麼可以在不建立或者使用內部欄位名的情況下呼叫eat()
方法。
還有第三個方法使用復合模式。我們可以建立乙個swimmer
介面,定義乙個swim()
方法和乙個swimmerimpl
型別嵌入到游泳運動員中。
type swimmer inte***ce
type trainer inte***ce
type swimmerimpl struct {}
func (s *swimmerimpl) swim()
type compositeswimmerb struct
用這種方法可以更明確的控制物件的建立。swimmer
欄位是嵌入的,但是不會被自動初始化,因為它指向乙個介面。正確的使用方法是:
swimmer := compositeswimmerb ,
&swimmerimpl{},
}swimmer.train()
swimmer.swim()
哪種方法更妙?個人來講,介面方法是最好的。首先,面向介面程式設計、其次,你不會把**的一部分留給編譯器的零初始化特性。這是乙個非常強大的特性,但必須小心使用,因為它會導致執行時問題,在編譯時使用介面時會發現這些問題。實際上,在不同的情況下,零初始化將在執行時為您節省時間!但我更喜歡盡可能多地使用介面,所以這實際上不是乙個選項。
另一種常見的符合模式是在模擬二叉樹時,需要在字段中儲存本身型別(左右孩子):
type tree struct
這是一種遞迴復合,並且由於遞迴的特性,我們必須用指標才能讓編譯器知道需要留多少記憶體。tree
這個結構體為每個例項儲存了leafvalue
物件,還有left
和right
中的兩個新tree
。
我們可以這樣建立乙個物件:
root := tree
left: nil,
},left: &tree
}// 0
// / \
// 4 5
// / \ / \
// n n n 6
// / \
// n n
我們在go中使用組合設計模式時,必須明確與繼承區分開。例如當我們把parent
結構嵌入到son
結構中:
type parent struct
type son struct
我們不能把son
當做乙個parent
,也就是說當乙個函式需要乙個parent
例項時不能把son
傳遞給他:
func getparentfield(p *parent) int
如果試圖傳遞乙個son
例項給getparentfield
,就會得到乙個錯誤資訊
cannot use son (type son) as type parent in argument to getparentfield如果非要這樣的話,可以吧
parent
作為乙個欄位而不是嵌入進son
中去:
type son struct
現在可以通過p
這個字段傳遞parent
給getparentfield
了:
son := son{}
getparentfield(son.p)
原始碼見
go 復合型別
1.陣列 1 陣列是擁有固定長度且擁有零個或者多個相同資料型別元素的序列。2 初始值預設為元素型別的零值 3 如果 出現在陣列長度的位置,則陣列的長度由初始化陣列的元素個數決定 4 陣列長度是陣列型別的一部分,3 int 和 4 int 是不同的型別,不能互相賦值 也就是說陣列長度在編譯時就已經確定...
GO 復合型別 切片
相當於長度可以擴張的陣列 經過陣列擷取的片段就是切片 func main01 擷取陣列的前9位 slice array 0 9 fmt.printf array的資料型別是 t n array fmt.printf slice的資料型別是 t,值是 v n slice,slice 下標為0開始擷取5...
設計模式 復合模式
復合模式原理 什麼是復合模式 模式常一起使用,組合在乙個設計解決方案中 復合模式在乙個解決方案中結合兩個或多個模式 能解決一般性或一系列的問題 某些模式結合使用,並不就是復合模式 複雜鴨子專案 多種鴨子,不同鴨子叫聲 飛行 游泳方式不同 策略模式 鵝,需要加入幾隻普通的鵝 介面卡模式 要統計鴨子叫聲...