復合模式 Go

2021-10-03 23:13:37 字數 4721 閱讀 7209

結構模式通過常用的結構和關係幫我們塑造應用,在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物件,還有leftright中的兩個新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這個字段傳遞parentgetparentfield了:

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...

設計模式 復合模式

復合模式原理 什麼是復合模式 模式常一起使用,組合在乙個設計解決方案中 復合模式在乙個解決方案中結合兩個或多個模式 能解決一般性或一系列的問題 某些模式結合使用,並不就是復合模式 複雜鴨子專案 多種鴨子,不同鴨子叫聲 飛行 游泳方式不同 策略模式 鵝,需要加入幾隻普通的鵝 介面卡模式 要統計鴨子叫聲...