Go語(yǔ)言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用
設(shè)計(jì)模式是軟件工程中各種常見(jiàn)問(wèn)題的經(jīng)典解決方案,設(shè)計(jì)模式不只是代碼,而是組織代碼的方式。假設(shè)一行行的代碼是磚,設(shè)計(jì)模式就是藍(lán)圖。
創(chuàng)建型模式
創(chuàng)建型模式是處理對(duì)象創(chuàng)建的設(shè)計(jì)模式,試圖根據(jù)實(shí)際情況使用合適的方式創(chuàng)建對(duì)象,增加已有代碼的靈活性和可復(fù)用性。
工廠方法模式 Factory Method
問(wèn)題
假設(shè)我們的業(yè)務(wù)需要一個(gè)支付渠道,我們開(kāi)發(fā)了一個(gè)Pay方法,其可以用于支付。請(qǐng)看以下示例:
type Pay interface { Pay() string } type PayReq struct { OrderId string // 訂單號(hào) } func (p *PayReq) Pay() string { // todo fmt.Println(p.OrderId) return "支付成功" }
如上,我們定義了接口Pay,并實(shí)現(xiàn)了其方法Pay()。
如果業(yè)務(wù)需求變更,需要我們提供多種支付方式,一種叫APay,一種叫BPay,這二種支付方式所需的參數(shù)不同,APay只需要訂單號(hào)OrderId,BPay則需要訂單號(hào)OrderId和Uid。此時(shí)如何修改?
很容易想到的是在原有的代碼基礎(chǔ)上修改,比如:
type Pay interface { APay() string BPay() string } type PayReq struct { OrderId string // 訂單號(hào) Uid int64 } func (p *PayReq) APay() string { // todo fmt.Println(p.OrderId) return "APay支付成功" } func (p *PayReq) BPay() string { // todo fmt.Println(p.OrderId) fmt.Println(p.Uid) return "BPay支付成功" }
我們?yōu)镻ay接口實(shí)現(xiàn)了APay() 和BPay() 方法。雖然暫時(shí)實(shí)現(xiàn)了業(yè)務(wù)需求,但卻使得結(jié)構(gòu)體PayReq變得冗余了,APay() 并不需要Uid參數(shù)。如果之后再增加CPay、DPay、EPay,可想而知,代碼會(huì)變得越來(lái)越難以維護(hù)。
隨著后續(xù)業(yè)務(wù)迭代,將不得不編寫(xiě)出復(fù)雜的代碼。
解決
讓我們想象一個(gè)工廠類(lèi),這個(gè)工廠類(lèi)需要生產(chǎn)電線和開(kāi)關(guān)等器具,我們可以為工廠類(lèi)提供一個(gè)生產(chǎn)方法,當(dāng)電線機(jī)器調(diào)用生產(chǎn)方法時(shí),就產(chǎn)出電線,當(dāng)開(kāi)關(guān)機(jī)器調(diào)用生產(chǎn)方法時(shí),就產(chǎn)出開(kāi)關(guān)。
套用到我們的支付業(yè)務(wù)來(lái),就是我們不再為接口提供APay方法、BPay方法,而只提供一個(gè)Pay方法,并將A支付方式和B支付方式的區(qū)別下放到子類(lèi)。
請(qǐng)看示例:
package factorymethod import "fmt" type Pay interface { Pay(string) int } type PayReq struct { OrderId string } type APayReq struct { PayReq } func (p *APayReq) Pay() string { // todo fmt.Println(p.OrderId) return "APay支付成功" } type BPayReq struct { PayReq Uid int64 } func (p *BPayReq) Pay() string { // todo fmt.Println(p.OrderId) fmt.Println(p.Uid) return "BPay支付成功" }
我們用APay和BPay兩個(gè)結(jié)構(gòu)體重寫(xiě)了Pay() 方法,如果需要添加一種新的支付方式, 只需要重寫(xiě)新的Pay() 方法即可。
工廠方法的優(yōu)點(diǎn)就在于避免了創(chuàng)建者和具體產(chǎn)品之間的緊密耦合,從而使得代碼更容易維護(hù)。
測(cè)試代碼:
package factorymethod import ( "testing" ) func TestPay(t *testing.T) { aPay := APayReq{} if aPay.Pay() != "APay支付成功" { t.Fatal("aPay error") } bPay := BPayReq{} if bPay.Pay() != "BPay支付成功" { t.Fatal("bPay error") } }
抽象工廠模式 Abstract Factory
問(wèn)題
抽象工廠模式基于工廠方法模式。兩者的區(qū)別在于:工廠方法模式是創(chuàng)建出一種產(chǎn)品,而抽象工廠模式是創(chuàng)建出一類(lèi)產(chǎn)品。這二種都屬于工廠模式,在設(shè)計(jì)上是相似的。
假設(shè),有一個(gè)存儲(chǔ)工廠,提供redis和mysql兩種存儲(chǔ)數(shù)據(jù)的方式。如果使用工廠方法模式,我們就需要一個(gè)存儲(chǔ)工廠,并提供SaveRedis方法和SaveMysql方法。
如果此時(shí)業(yè)務(wù)還需要分成存儲(chǔ)散文和古詩(shī)兩種載體,這兩種載體都可以進(jìn)行redis和mysql存儲(chǔ)。就可以使用抽象工廠模式,我們需要一個(gè)存儲(chǔ)工廠作為父工廠,散文工廠和古詩(shī)工廠作為子工廠,并提供SaveRedis方法和SaveMysql方法。
解決
以上文的存儲(chǔ)工廠業(yè)務(wù)為例,用抽象工廠模式的思路來(lái)設(shè)計(jì)代碼,就像下面這樣:
package abstractfactory import "fmt" // SaveArticle 抽象模式工廠接口 type SaveArticle interface { CreateProse() Prose CreateAncientPoetry() AncientPoetry } type SaveRedis struct{} func (*SaveRedis) CreateProse() Prose { return &RedisProse{} } func (*SaveRedis) CreateAncientPoetry() AncientPoetry { return &RedisProse{} } type SaveMysql struct{} func (*SaveMysql) CreateProse() Prose { return &MysqlProse{} } func (*SaveMysql) CreateAncientPoetry() AncientPoetry { return &MysqlProse{} } // Prose 散文 type Prose interface { SaveProse() } // AncientPoetry 古詩(shī) type AncientPoetry interface { SaveAncientPoetry() } type RedisProse struct{} func (*RedisProse) SaveProse() { fmt.Println("Redis Save Prose") } func (*RedisProse) SaveAncientPoetry() { fmt.Println("Redis Save Ancient Poetry") } type MysqlProse struct{} func (*MysqlProse) SaveProse() { fmt.Println("Mysql Save Prose") } func (*MysqlProse) SaveAncientPoetry() { fmt.Println("Mysql Save Ancient Poetry") }
我們定義了存儲(chǔ)工廠,也就是SaveArticle接口,并實(shí)現(xiàn)了CreateProse方法和CreateAncientPoetry方法,這2個(gè)方法分別用于創(chuàng)建散文工廠和古詩(shī)工廠。
然后我們又分別為散文工廠和古詩(shī)工廠實(shí)現(xiàn)了SaveProse方法和SaveAncientPoetry方法,并用Redis結(jié)構(gòu)體和Mysql結(jié)構(gòu)體分別重寫(xiě)了2種存儲(chǔ)方法。
測(cè)試代碼:
package abstractfactory func Save(saveArticle SaveArticle) { saveArticle.CreateProse().SaveProse() saveArticle.CreateAncientPoetry().SaveAncientPoetry() } func ExampleSaveRedis() { var factory SaveArticle factory = &SaveRedis{} Save(factory) // Output: // Redis Save Prose // Redis Save Ancient Poetry } func ExampleSaveMysql() { var factory SaveArticle factory = &SaveMysql{} Save(factory) // Output: // Mysql Save Prose // Mysql Save Ancient Poetry }
建造者模式 Builder
問(wèn)題
假設(shè)業(yè)務(wù)需要按步驟創(chuàng)建一系列復(fù)雜的對(duì)象,實(shí)現(xiàn)這些步驟的代碼加在一起非常繁復(fù),我們可以將這些代碼放進(jìn)一個(gè)包含了眾多參數(shù)的構(gòu)造函數(shù)中,但這個(gè)構(gòu)造函數(shù)看起來(lái)將會(huì)非常雜亂無(wú)章,且難以維護(hù)。
假設(shè)業(yè)務(wù)需要建造一個(gè)房子對(duì)象,需要先打地基、建墻、建屋頂、建花園、放置家具……。我們需要非常多的步驟,并且這些步驟之間是有聯(lián)系的,即使將各個(gè)步驟從一個(gè)大的構(gòu)造函數(shù)抽出到其他小函數(shù)中,整個(gè)程序的層次結(jié)構(gòu)看起來(lái)依然很復(fù)雜。
如何解決呢?像這種復(fù)雜的有許多步驟的構(gòu)造函數(shù),就可以用建造者模式來(lái)設(shè)計(jì)。
建造者模式的用處就在于能夠分步驟創(chuàng)建復(fù)雜對(duì)象。
解決
在建造者模式中,我們需要清晰的定義每個(gè)步驟的代碼,然后在一個(gè)構(gòu)造函數(shù)中操作這些步驟,我們需要一個(gè)主管類(lèi),用這個(gè)主管類(lèi)來(lái)管理各步驟。這樣我們就只需要將所需參數(shù)傳給一個(gè)構(gòu)造函數(shù),構(gòu)造函數(shù)再將參數(shù)傳遞給對(duì)應(yīng)的主管類(lèi),最后由主管類(lèi)完成后續(xù)所有建造任務(wù)。
請(qǐng)看以下代碼:
package builder import "fmt" // 建造者接口 type Builder interface { Part1() Part2() Part3() } // 管理類(lèi) type Director struct { builder Builder } // 構(gòu)造函數(shù) func NewDirector(builder Builder) *Director { return &Director{ builder: builder, } } // 建造 func (d *Director) Construct() { d.builder.Part1() d.builder.Part2() d.builder.Part3() } type Builder struct {} func (b *Builder) Part1() { fmt.Println("part1") } func (b *Builder) Part2() { fmt.Println("part2") } func (b *Builder) Part3() { fmt.Println("part3") }
如上,我們實(shí)現(xiàn)part1、part2、part3這3個(gè)步驟,只需要執(zhí)行構(gòu)造函數(shù),對(duì)應(yīng)的管理類(lèi)就可以運(yùn)行建造方法Construct,完成3個(gè)步驟的執(zhí)行。
測(cè)試代碼:
package builder func ExampleBuilder() { builder := &Builder{} director := NewDirector(builder) director.Construct() // Output: // part1 // part2 // part3 }
原型模式 Prototype
問(wèn)題
如果你希望生成一個(gè)對(duì)象,其與另一個(gè)對(duì)象完全相同,該如何實(shí)現(xiàn)呢?
如果遍歷對(duì)象的所有成員,將其依次復(fù)制到新對(duì)象中,會(huì)稍顯麻煩,而且有些對(duì)象可能會(huì)有私有成員變量遺漏。
原型模式將這個(gè)克隆的過(guò)程委派給了被克隆的實(shí)際對(duì)象,被克隆的對(duì)象就叫做“原型”。
解決
如果需要克隆一個(gè)新的對(duì)象,這個(gè)對(duì)象完全獨(dú)立于它的原型,那么就可以使用原型模式。
原型模式的實(shí)現(xiàn)非常簡(jiǎn)單,請(qǐng)看以下代碼:
package prototype import "testing" var manager *PrototypeManager type Type1 struct { name string } func (t *Type1) Clone() *Type1 { tc := *t return &tc } func TestClone(t *testing.T) { t1 := &Type1{ name: "type1", } t2 := t1.Clone() if t1 == t2 { t.Fatal("error! get clone not working") } }
我們依靠一個(gè)Clone方法實(shí)現(xiàn)了原型Type1的克隆。
原型模式的用處就在于我們可以克隆對(duì)象,而無(wú)需與原型對(duì)象的依賴(lài)相耦合。
單例模式 Singleton
問(wèn)題
存儲(chǔ)著重要對(duì)象的全局變量,往往意味著“不安全”,因?yàn)槟銦o(wú)法保證這個(gè)全局變量的值不會(huì)在項(xiàng)目的某個(gè)引用處被覆蓋掉。
對(duì)數(shù)據(jù)的修改經(jīng)常導(dǎo)致出乎意料的的結(jié)果和難以發(fā)現(xiàn)的bug。我在一處更新數(shù)據(jù),卻沒(méi)有意識(shí)到軟件中的另一處期望著完全不同的數(shù)據(jù),于是一個(gè)功能就失效了,而且找出故障的原因也會(huì)非常困難。
一個(gè)較好的解決方案是:將這樣的“可變數(shù)據(jù)”封裝起來(lái),寫(xiě)一個(gè)查詢(xún)方法專(zhuān)門(mén)用來(lái)獲取這些值。
單例模式則更進(jìn)一步:除了要為“可變數(shù)據(jù)”提供一個(gè)全局訪問(wèn)方法,它還要保證獲取到的只有同一個(gè)實(shí)例。也就是說(shuō),如果你打算用一個(gè)構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象,單例模式將保證你得到的不是一個(gè)新的對(duì)象,而是之前創(chuàng)建過(guò)的對(duì)象,并且每次它所返回的都只有這同一個(gè)對(duì)象,也就是單例。這可以保護(hù)該對(duì)象實(shí)例不被篡改。
解決
單例模式需要一個(gè)全局構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)會(huì)返回一個(gè)私有的對(duì)象,無(wú)論何時(shí)調(diào)用,它總是返回相同的對(duì)象。
請(qǐng)看以下代碼:
package singleton import ( "sync" ) // 單例實(shí)例 type singleton struct { Value int } type Singleton interface { getValue() int } func (s singleton) getValue() int { return s.Value } var ( instance *singleton once sync.Once ) // 構(gòu)造方法,用于獲取單例模式對(duì)象 func GetInstance(v int) Singleton { once.Do(func() { instance = &singleton{Value: v} }) return instance }
單例實(shí)例singleton被保存為一個(gè)私有的變量,以保證不被其他包的函數(shù)引用。
用構(gòu)造方法GetInstance可以獲得單例實(shí)例,函數(shù)中使用了sync包的once方法,以保證實(shí)例只會(huì)在首次調(diào)用時(shí)被初始化一次,之后再調(diào)用構(gòu)造方法都只會(huì)返回同一個(gè)實(shí)例。
測(cè)試代碼:
func TestSingleton(t *testing.T) { ins1 := GetInstance2(1) ins2 := GetInstance2(2) if ins1 != ins2 { t.Fatal("instance is not equal") } }
如果你需要更加嚴(yán)格地控制全局變量,這確實(shí)很有必要,那么就使用單例模式吧。
結(jié)構(gòu)型模式
結(jié)構(gòu)型模式將一些對(duì)象和類(lèi)組裝成更大的結(jié)構(gòu)體,并同時(shí)保持結(jié)構(gòu)的靈活和高效。
適配器模式 Adapter
問(wèn)題
適配器模式說(shuō)白了就是兼容。
假設(shè)一開(kāi)始我們提供了A對(duì)象,后期隨著業(yè)務(wù)迭代,又需要從A對(duì)象的基礎(chǔ)之上衍生出不同的需求。如果有很多函數(shù)已經(jīng)在線上調(diào)用了A對(duì)象,此時(shí)再對(duì)A對(duì)象進(jìn)行修改就比較麻煩,因?yàn)樾枰紤]兼容問(wèn)題。還有更糟糕的情況, 你可能沒(méi)有程序庫(kù)的源代碼, 從而無(wú)法對(duì)其進(jìn)行修改。
此時(shí)就可以用一個(gè)適配器,它就像一個(gè)接口轉(zhuǎn)換器,調(diào)用方只需要調(diào)用這個(gè)適配器接口,而不需要關(guān)注其背后的實(shí)現(xiàn),由適配器接口封裝復(fù)雜的過(guò)程。
解決
假設(shè)有2個(gè)接口,一個(gè)將厘米轉(zhuǎn)為米,一個(gè)將米轉(zhuǎn)為厘米。我們提供一個(gè)適配器接口,使調(diào)用方不需要再操心調(diào)用哪個(gè)接口,直接由適配器做好兼容。
請(qǐng)看以下代碼:
package adapter // 提供一個(gè)獲取米的接口和一個(gè)獲取厘米的接口 type Cm interface { getLength(float64) float64 } type M interface { getLength(float64) float64 } func NewM() M { return &getLengthM{} } type getLengthM struct{} func (*getLengthM) getLength(cm float64) float64 { return cm / 10 } func NewCm() Cm { return &getLengthCm{} } type getLengthCm struct{} func (a *getLengthCm) getLength(m float64) float64 { return m * 10 } // 適配器 type LengthAdapter interface { getLength(string, float64) float64 } func NewLengthAdapter() LengthAdapter { return &getLengthAdapter{} } type getLengthAdapter struct{} func (*getLengthAdapter) getLength(isType string, into float64) float64 { if isType == "m" { return NewM().getLength(into) } return NewCm().getLength(into) }
上面實(shí)現(xiàn)了Cm和M兩個(gè)接口,并由適配器LengthAdapter做兼容。
測(cè)試代碼:
package adapter import "testing" func TestAdapter(t *testing.T) { into := 10.5 getLengthAdapter := NewLengthAdapter().getLength("m", into) getLengthM := NewM().getLength(into) if getLengthAdapter != getLengthM { t.Fatalf("getLengthAdapter: %f, getLengthM: %f", getLengthAdapter, getLengthM) } }
橋接模式Bridge
問(wèn)題
假設(shè)一開(kāi)始業(yè)務(wù)需要兩種發(fā)送信息的渠道,sms和email,我們可以分別實(shí)現(xiàn)sms和email兩個(gè)接口。
之后隨著業(yè)務(wù)迭代,又產(chǎn)生了新的需求,需要提供兩種系統(tǒng)發(fā)送方式,systemA和systemB,并且這兩種系統(tǒng)發(fā)送方式都應(yīng)該支持sms和email渠道。
此時(shí)至少需要提供4種方法:systemA to sms,systemA to email,systemB to sms,systemB to email。
如果再分別增加一種渠道和一種系統(tǒng)發(fā)送方式,就需要提供9種方法。這將導(dǎo)致代碼的復(fù)雜程度指數(shù)增長(zhǎng)。
解決
其實(shí)之前我們是在用繼承的想法來(lái)看問(wèn)題,橋接模式則希望將繼承關(guān)系轉(zhuǎn)變?yōu)殛P(guān)聯(lián)關(guān)系,使兩個(gè)類(lèi)獨(dú)立存在。
詳細(xì)說(shuō)一下:
- 橋接模式需要將抽象和實(shí)現(xiàn)區(qū)分開(kāi);
- 橋接模式需要將“渠道”和“系統(tǒng)發(fā)送方式”這兩種類(lèi)別區(qū)分開(kāi);
- 最后在“系統(tǒng)發(fā)送方式”的類(lèi)里調(diào)用“渠道”的抽象接口,使他們從繼承關(guān)系轉(zhuǎn)變?yōu)殛P(guān)聯(lián)關(guān)系。
用一句話(huà)總結(jié)橋接模式的理念,就是:“將抽象與實(shí)現(xiàn)解耦,將不同類(lèi)別的繼承關(guān)系改為關(guān)聯(lián)關(guān)系。 ”
請(qǐng)看以下代碼:
package bridge import "fmt" // 兩種發(fā)送消息的方法 type SendMessage interface { send(text, to string) } type sms struct{} func NewSms() SendMessage { return &sms{} } func (*sms) send(text, to string) { fmt.Println(fmt.Sprintf("send %s to %s sms", text, to)) } type email struct{} func NewEmail() SendMessage { return &email{} } func (*email) send(text, to string) { fmt.Println(fmt.Sprintf("send %s to %s email", text, to)) } // 兩種發(fā)送系統(tǒng) type systemA struct { method SendMessage } func NewSystemA(method SendMessage) *systemA { return &systemA{ method: method, } } func (m *systemA) SendMessage(text, to string) { m.method.send(fmt.Sprintf("[System A] %s", text), to) } type systemB struct { method SendMessage } func NewSystemB(method SendMessage) *systemB { return &systemB{ method: method, } } func (m *systemB) SendMessage(text, to string) { m.method.send(fmt.Sprintf("[System B] %s", text), to) }
可以看到我們先定義了sms和email二種實(shí)現(xiàn),以及接口SendMessage。接著我們實(shí)現(xiàn)了systemA和systemB,并調(diào)用了抽象接口SendMessage。
測(cè)試代碼:
package bridge func ExampleSystemA() { NewSystemA(NewSms()).SendMessage("hi", "baby") NewSystemA(NewEmail()).SendMessage("hi", "baby") // Output: // send [System A] hi to baby sms // send [System A] hi to baby email } func ExampleSystemB() { NewSystemB(NewSms()).SendMessage("hi", "baby") NewSystemB(NewEmail()).SendMessage("hi", "baby") // Output: // send [System B] hi to baby sms // send [System B] hi to baby email }
如果你想要拆分或重組一個(gè)具有多重功能的復(fù)雜類(lèi),可以使用橋接模式。
對(duì)象樹(shù)模式Object Tree
問(wèn)題
在項(xiàng)目中,如果我們需要用到樹(shù)狀結(jié)構(gòu),就可以使用對(duì)象樹(shù)模式。換言之,如果項(xiàng)目的核心模型不能以樹(shù)狀結(jié)構(gòu)表示,則沒(méi)必要使用對(duì)象樹(shù)模式。
對(duì)象樹(shù)模式的用處就在于可以利用多態(tài)和遞歸機(jī)制更方便地使用復(fù)雜樹(shù)結(jié)構(gòu)。
解決
請(qǐng)看以下代碼:
package objecttree import "fmt" type Component interface { Parent() Component SetParent(Component) Name() string SetName(string) AddChild(Component) Search(string) } const ( LeafNode = iota CompositeNode ) func NewComponent(kind int, name string) Component { var c Component switch kind { case LeafNode: c = NewLeaf() case CompositeNode: c = NewComposite() } c.SetName(name) return c } type component struct { parent Component name string } func (c *component) Parent() Component { return c.parent } func (c *component) SetParent(parent Component) { c.parent = parent } func (c *component) Name() string { return c.name } func (c *component) SetName(name string) { c.name = name } func (c *component) AddChild(Component) {} type Leaf struct { component } func NewLeaf() *Leaf { return &Leaf{} } func (c *Leaf) Search(pre string) { fmt.Printf("leaf %s-%s\n", pre, c.Name()) } type Composite struct { component childs []Component } func NewComposite() *Composite { return &Composite{ childs: make([]Component, 0), } } func (c *Composite) AddChild(child Component) { child.SetParent(c) c.childs = append(c.childs, child) } func (c *Composite) Search(pre string) { fmt.Printf("%s+%s\n", pre, c.Name()) pre += " " for _, comp := range c.childs { comp.Search(pre) } }
在Search方法中使用遞歸打印出了整棵樹(shù)結(jié)構(gòu)。
測(cè)試代碼:
package objecttree func ExampleComposite() { root := NewComponent(CompositeNode, "root") c1 := NewComponent(CompositeNode, "c1") c2 := NewComponent(CompositeNode, "c2") c3 := NewComponent(CompositeNode, "c3") l1 := NewComponent(LeafNode, "l1") l2 := NewComponent(LeafNode, "l2") l3 := NewComponent(LeafNode, "l3") root.AddChild(c1) root.AddChild(c2) c1.AddChild(c3) c1.AddChild(l1) c2.AddChild(l2) c2.AddChild(l3) root.Search("") // Output: // +root // +c1 // +c3 //leaf -l1 // +c2 //leaf -l2 //leaf -l3 }
裝飾模式Decorator
問(wèn)題
有時(shí)候我們需要在一個(gè)類(lèi)的基礎(chǔ)上擴(kuò)展另一個(gè)類(lèi),例如,一個(gè)披薩類(lèi),你可以在披薩類(lèi)的基礎(chǔ)上增加番茄披薩類(lèi)和芝士披薩類(lèi)。此時(shí)就可以使用裝飾模式,簡(jiǎn)單來(lái)說(shuō),裝飾模式就是將對(duì)象封裝到另一個(gè)對(duì)象中,用以為原對(duì)象綁定新的行為。
如果你希望在無(wú)需修改代碼的情況下使用對(duì)象,并且希望為對(duì)象新增額外的行為,就可以考慮使用裝飾模式。
解決
用上文的披薩類(lèi)做例子。請(qǐng)看以下代碼:
package decorator type pizza interface { getPrice() int } type base struct {} func (p *base) getPrice() int { return 15 } type tomatoTopping struct { pizza pizza } func (c *tomatoTopping) getPrice() int { pizzaPrice := c.pizza.getPrice() return pizzaPrice + 10 } type cheeseTopping struct { pizza pizza } func (c *cheeseTopping) getPrice() int { pizzaPrice := c.pizza.getPrice() return pizzaPrice + 20 }
首先我們定義了pizza接口,創(chuàng)建了base類(lèi),實(shí)現(xiàn)了方法getPrice。然后再用裝飾模式的理念,實(shí)現(xiàn)了tomatoTopping和cheeseTopping類(lèi),他們都封裝了pizza接口的getPrice方法。
測(cè)試代碼:
package decorator import "fmt" func ExampleDecorator() { pizza := &base{} //Add cheese topping pizzaWithCheese := &cheeseTopping{ pizza: pizza, } //Add tomato topping pizzaWithCheeseAndTomato := &tomatoTopping{ pizza: pizzaWithCheese, } fmt.Printf("price is %d\n", pizzaWithCheeseAndTomato.getPrice()) // Output: // price is 45 }
外觀模式Facade
問(wèn)題
如果你需要初始化大量復(fù)雜的庫(kù)或框架,就需要管理其依賴(lài)關(guān)系并且按正確的順序執(zhí)行。此時(shí)就可以用一個(gè)外觀類(lèi)來(lái)統(tǒng)一處理這些依賴(lài)關(guān)系,以對(duì)其進(jìn)行整合。
解決
外觀模式和建造者模式很相似。兩者的區(qū)別在于,外觀模式是一種結(jié)構(gòu)型模式,她的目的是將對(duì)象組合起來(lái),而不是像建造者模式那樣創(chuàng)建出不同的產(chǎn)品。
請(qǐng)看以下代碼:
package facade import "fmt" // 初始化APIA和APIB type APIA interface { TestA() string } func NewAPIA() APIA { return &apiRunA{} } type apiRunA struct{} func (*apiRunA) TestA() string { return "A api running" } type APIB interface { TestB() string } func NewAPIB() APIB { return &apiRunB{} } type apiRunB struct{} func (*apiRunB) TestB() string { return "B api running" } // 外觀類(lèi) type API interface { Test() string } func NewAPI() API { return &apiRun{ a: NewAPIA(), b: NewAPIB(), } } type apiRun struct { a APIA b APIB } func (a *apiRun) Test() string { aRet := a.a.TestA() bRet := a.b.TestB() return fmt.Sprintf("%s\n%s", aRet, bRet) }
假設(shè)要初始化APIA和APIB,我們就可以通過(guò)一個(gè)外觀類(lèi)API進(jìn)行處理,在外觀類(lèi)接口Test方法中分別執(zhí)行類(lèi)TestA方法和TestB方法。
測(cè)試代碼:
package facade import "testing" var expect = "A api running\nB api running" // TestFacadeAPI ... func TestFacadeAPI(t *testing.T) { api := NewAPI() ret := api.Test() if ret != expect { t.Fatalf("expect %s, return %s", expect, ret) } }
享元模式 Flyweight
問(wèn)題
在一些情況下,程序沒(méi)有足夠的內(nèi)存容量支持存儲(chǔ)大量對(duì)象,或者大量的對(duì)象存儲(chǔ)著重復(fù)的狀態(tài),此時(shí)就會(huì)造成內(nèi)存資源的浪費(fèi)。
享元模式提出了這樣的解決方案:如果多個(gè)對(duì)象中相同的狀態(tài)可以共用,就能在在有限的內(nèi)存容量中載入更多對(duì)象。
解決
如上所說(shuō),享元模式希望抽取出能在多個(gè)對(duì)象間共享的重復(fù)狀態(tài)。
我們可以使用map結(jié)構(gòu)來(lái)實(shí)現(xiàn)這一設(shè)想,假設(shè)需要存儲(chǔ)一些代表顏色的對(duì)象,使用享元模式可以這樣做,請(qǐng)看以下代碼:
package flyweight import "fmt" // 享元工廠 type ColorFlyweightFactory struct { maps map[string]*ColorFlyweight } var colorFactory *ColorFlyweightFactory func GetColorFlyweightFactory() *ColorFlyweightFactory { if colorFactory == nil { colorFactory = &ColorFlyweightFactory{ maps: make(map[string]*ColorFlyweight), } } return colorFactory } func (f *ColorFlyweightFactory) Get(filename string) *ColorFlyweight { color := f.maps[filename] if color == nil { color = NewColorFlyweight(filename) f.maps[filename] = color } return color } type ColorFlyweight struct { data string } // 存儲(chǔ)color對(duì)象 func NewColorFlyweight(filename string) *ColorFlyweight { // Load color file data := fmt.Sprintf("color data %s", filename) return &ColorFlyweight{ data: data, } } type ColorViewer struct { *ColorFlyweight } func NewColorViewer(name string) *ColorViewer { color := GetColorFlyweightFactory().Get(name) return &ColorViewer{ ColorFlyweight: color, } }
我們定義了一個(gè)享元工廠,使用map存儲(chǔ)相同對(duì)象(key)的狀態(tài)(value)。這個(gè)享元工廠可以使我們更方便和安全的訪問(wèn)各種享元,保證其狀態(tài)不被修改。
我們定義了NewColorViewer方法,它會(huì)調(diào)用享元工廠的Get方法存儲(chǔ)對(duì)象,而在享元工廠的實(shí)現(xiàn)中可以看到,相同狀態(tài)的對(duì)象只會(huì)占用一次。
測(cè)試代碼:
package flyweight import "testing" func TestFlyweight(t *testing.T) { viewer1 := NewColorViewer("blue") viewer2 := NewColorViewer("blue") if viewer1.ColorFlyweight != viewer2.ColorFlyweight { t.Fail() } }
當(dāng)程序需要存儲(chǔ)大量對(duì)象且沒(méi)有足夠的內(nèi)存容量時(shí),可以考慮使用享元模式。
代理模式Proxy
問(wèn)題
如果你需要在訪問(wèn)一個(gè)對(duì)象時(shí),有一個(gè)像“代理”一樣的角色,她可以在訪問(wèn)對(duì)象之前為你進(jìn)行緩存檢查、權(quán)限判斷等訪問(wèn)控制,在訪問(wèn)對(duì)象之后為你進(jìn)行結(jié)果緩存、日志記錄等結(jié)果處理,那么就可以考慮使用代理模式。
回憶一下一些web框架的router模塊,當(dāng)客戶(hù)端訪問(wèn)一個(gè)接口時(shí),在最終執(zhí)行對(duì)應(yīng)的接口之前,router模塊會(huì)執(zhí)行一些事前操作,進(jìn)行權(quán)限判斷等操作,在執(zhí)行之后還會(huì)記錄日志,這就是典型的代理模式。
解決
代理模式需要一個(gè)代理類(lèi),其包含執(zhí)行真實(shí)對(duì)象所需的成員變量,并由代理類(lèi)管理整個(gè)生命周期。
請(qǐng)看以下代碼:
package proxy import "fmt" type Subject interface { Proxy() string } // 代理 type Proxy struct { real RealSubject } func (p Proxy) Proxy() string { var res string // 在調(diào)用真實(shí)對(duì)象之前,檢查緩存,判斷權(quán)限,等等 p.real.Pre() // 調(diào)用真實(shí)對(duì)象 p.real.Real() // 調(diào)用之后的操作,如緩存結(jié)果,對(duì)結(jié)果進(jìn)行處理,等等 p.real.After() return res } // 真實(shí)對(duì)象 type RealSubject struct{} func (RealSubject) Real() { fmt.Print("real") } func (RealSubject) Pre() { fmt.Print("pre:") } func (RealSubject) After() { fmt.Print(":after") }
我們定義了代理類(lèi)Proxy,執(zhí)行Proxy之后,在調(diào)用真實(shí)對(duì)象Real之前,我們會(huì)先調(diào)用事前對(duì)象Pre,并在執(zhí)行真實(shí)對(duì)象Real之后,調(diào)用事后對(duì)象After。
測(cè)試代碼:
package proxy func ExampleProxy() { var sub Subject sub = &Proxy{} sub.Proxy() // Output: // pre:real:after }
行為型模式
行為型模式處理對(duì)象和類(lèi)之間的通信,并使其保持高效的溝通和委派。
責(zé)任鏈模式Chain of Responsibility
問(wèn)題
假設(shè)我們要讓程序按照指定的步驟執(zhí)行,并且這個(gè)步驟的順序不是固定的,而是可以根據(jù)不同需求改變的,每個(gè)步驟都會(huì)對(duì)請(qǐng)求進(jìn)行一些處理,并將結(jié)果傳遞給下一個(gè)步驟的處理者,就像一條流水線一樣,我們?cè)撊绾螌?shí)現(xiàn)?
當(dāng)遇到這種必須按順序執(zhí)行多個(gè)處理者,并且處理者的順序可以改變的需求,我們可以考慮使用責(zé)任鏈模式。
解決
責(zé)任鏈模式使用了類(lèi)似鏈表的結(jié)構(gòu)。請(qǐng)看以下代碼:
package chain import "fmt" type department interface { execute(*Do) setNext(department) } type aPart struct { next department } func (r *aPart) execute(p *Do) { if p.aPartDone { fmt.Println("aPart done") r.next.execute(p) return } fmt.Println("aPart") p.aPartDone = true r.next.execute(p) } func (r *aPart) setNext(next department) { r.next = next } type bPart struct { next department } func (d *bPart) execute(p *Do) { if p.bPartDone { fmt.Println("bPart done") d.next.execute(p) return } fmt.Println("bPart") p.bPartDone = true d.next.execute(p) } func (d *bPart) setNext(next department) { d.next = next } type endPart struct { next department } func (c *endPart) execute(p *Do) { if p.endPartDone { fmt.Println("endPart Done") } fmt.Println("endPart") } func (c *endPart) setNext(next department) { c.next = next } type Do struct { aPartDone bool bPartDone bool endPartDone bool }
我們實(shí)現(xiàn)了方法execute和setNext,并定義了aPart、bPart、endPart這3個(gè)處理者,每個(gè)處理者都可以通過(guò)execute方法執(zhí)行其對(duì)應(yīng)的業(yè)務(wù)代碼,并可以通過(guò)setNext方法決定下一個(gè)處理者是誰(shuí)。除了endPart是最終的處理者之外,在它之前的處理者aPart、bPart的順序都可以任意調(diào)整。
請(qǐng)看以下測(cè)試代碼:
func ExampleChain() { startPart := &endPart{} aPart := &aPart{} aPart.setNext(startPart) bPart := &bPart{} bPart.setNext(aPart) do := &Do{} bPart.execute(do) // Output: // bPart // aPart // endPart }
我們也可以調(diào)整處理者的執(zhí)行順序:
func ExampleChain2() { startPart := &endPart{} bPart := &bPart{} bPart.setNext(startPart) aPart := &aPart{} aPart.setNext(bPart) do := &Do{} aPart.execute(do) // Output: // aPart // bPart // endPart }
命令模式Command
問(wèn)題
假設(shè)你實(shí)現(xiàn)了開(kāi)啟和關(guān)閉電視機(jī)的功能,隨著業(yè)務(wù)迭代,還需要實(shí)現(xiàn)開(kāi)啟和關(guān)閉冰箱的功能,開(kāi)啟和關(guān)閉電燈的功能,開(kāi)啟和關(guān)閉微波爐的功能……這些功能都基于你的基類(lèi),開(kāi)啟和關(guān)閉。如果你之后對(duì)基類(lèi)進(jìn)行修改,很可能會(huì)影響到其他功能,這使項(xiàng)目變得不穩(wěn)定了。
一個(gè)優(yōu)秀的設(shè)計(jì)往往會(huì)關(guān)注于軟件的分層與解耦,命令模式試圖做到這樣的結(jié)果:讓命令和對(duì)應(yīng)功能解耦,并能根據(jù)不同的請(qǐng)求將其方法參數(shù)化。
解決
還是用開(kāi)啟和關(guān)閉家用電器的例子來(lái)舉例吧。請(qǐng)看以下代碼:
package command import "fmt" // 請(qǐng)求者 type button struct { command command } func (b *button) press() { b.command.execute() } // 具體命令接口 type command interface { execute() } type onCommand struct { device device } func (c *onCommand) execute() { c.device.on() } type offCommand struct { device device } func (c *offCommand) execute() { c.device.off() } // 接收者 type device interface { on() off() } type tv struct{} func (t *tv) on() { fmt.Println("Turning tv on") } func (t *tv) off() { fmt.Println("Turning tv off") } type airConditioner struct{} func (t *airConditioner) on() { fmt.Println("Turning air conditioner on") } func (t *airConditioner) off() { fmt.Println("Turning air conditioner off") }
我們分別實(shí)現(xiàn)了請(qǐng)求者button,命令接口command,接收者device。請(qǐng)求者button就像是那個(gè)可以執(zhí)行開(kāi)啟或關(guān)閉的遙控器,命令接口command則是一個(gè)中間層,它使我們的請(qǐng)求者和接收者解藕。
測(cè)試代碼:
package command func ExampleCommand() { Tv() AirConditioner() // Output: // Turning tv on // Turning tv off // Turning air conditioner on // Turning air conditioner off } func Tv() { tv := &tv{} onTvCommand := &onCommand{ device: tv, } offTvCommand := &offCommand{ device: tv, } onTvButton := &button{ command: onTvCommand, } onTvButton.press() offTvButton := &button{ command: offTvCommand, } offTvButton.press() } func AirConditioner() { airConditioner := &airConditioner{} onAirConditionerCommand := &onCommand{ device: airConditioner, } offAirConditionerCommand := &offCommand{ device: airConditioner, } onAirConditionerButton := &button{ command: onAirConditionerCommand, } onAirConditionerButton.press() offAirConditionerButton := &button{ command: offAirConditionerCommand, } offAirConditionerButton.press() }
迭代器模式Iterator
問(wèn)題
迭代器模式用于遍歷集合中的元素,無(wú)論集合的數(shù)據(jù)結(jié)構(gòu)是怎樣的。
解決
請(qǐng)看以下代碼:
package iterator // 集合接口 type collection interface { createIterator() iterator } // 具體的集合 type part struct { title string number int } type partCollection struct { part parts []*part } func (u *partCollection) createIterator() iterator { return &partIterator{ parts: u.parts, } } // 迭代器 type iterator interface { hasNext() bool getNext() *part } // 具體的迭代器 type partIterator struct { index int parts []*part } func (u *partIterator) hasNext() bool { if u.index < len(u.parts) { return true } return false } func (u *partIterator) getNext() *part { if u.hasNext() { part := u.parts[u.index] u.index++ return part } return nil }
測(cè)試代碼:
func ExampleIterator() { part1 := &part{ title: "part1", number: 10, } part2 := &part{ title: "part2", number: 20, } part3 := &part{ title: "part3", number: 30, } partCollection := &partCollection{ parts: []*part{part1, part2, part3}, } iterator := partCollection.createIterator() for iterator.hasNext() { part := iterator.getNext() fmt.Println(part) } // Output: // &{part1 10} // &{part2 20} // &{part3 30} }
中介者模式Mediator
問(wèn)題
中介者模式試圖解決網(wǎng)狀關(guān)系的復(fù)雜關(guān)聯(lián),降低對(duì)象間的耦合度。
舉個(gè)例子,假設(shè)一個(gè)十字路口上的車(chē)都是對(duì)象,它們會(huì)執(zhí)行不同的操作,前往不同的目的地,那么在十字路口指揮的交警就是“中介者”。
各個(gè)對(duì)象通過(guò)執(zhí)行中介者接口,再由中介者維護(hù)對(duì)象之間的聯(lián)系。這能使對(duì)象變得更獨(dú)立,比較適合用在一些對(duì)象是網(wǎng)狀關(guān)系的案例上。
解決
假設(shè)有p1,p2,p3這3個(gè)發(fā)送者,p1 發(fā)送的消息p2能收到,p2 發(fā)送的消息p1能收到,p3 發(fā)送的消息則p1和p2能收到,如何實(shí)現(xiàn)呢?像這種情況就很適合用中介者模式實(shí)現(xiàn)。
請(qǐng)看以下代碼:
package mediator import ( "fmt" ) type p1 struct{} func (p *p1) getMessage(data string) { fmt.Println("p1 get message: " + data) } type p2 struct{} func (p *p2) getMessage(data string) { fmt.Println("p2 get message: " + data) } type p3 struct{} func (p *p3) getMessage(data string) { fmt.Println("p3 get message: " + data) } type Message struct { p1 *p1 p2 *p2 p3 *p3 } func (m *Message) sendMessage(i interface{}, data string) { switch i.(type) { case *p1: m.p2.getMessage(data) case *p2: m.p1.getMessage(data) case *p3: m.p1.getMessage(data) m.p2.getMessage(data) } }
我們定義了p1,p2,p3這3個(gè)對(duì)象,然后實(shí)現(xiàn)了中介者sendMessage。
測(cè)試代碼:
package mediator func ExampleMediator() { message := &Message{} p1 := &p1{} p2 := &p2{} p3 := &p3{} message.sendMessage(p1, "hi! my name is p1") message.sendMessage(p2, "hi! my name is p2") message.sendMessage(p3, "hi! my name is p3") // Output: // p2 get message: hi! my name is p1 // p1 get message: hi! my name is p2 // p1 get message: hi! my name is p3 // p2 get message: hi! my name is p3 }
備忘錄模式Memento
問(wèn)題
常用的文字編輯器都支持保存和恢復(fù)一段文字的操作,如果我們想要在程序中實(shí)現(xiàn)保存和恢復(fù)的功能該怎么做呢?
我們需要提供保存和恢復(fù)的功能,當(dāng)保存功能被調(diào)用時(shí),就會(huì)生成當(dāng)前對(duì)象的快照,在恢復(fù)功能被調(diào)用時(shí),就會(huì)用之前保存的快照覆蓋當(dāng)前的快照。這可以使用備忘錄模式來(lái)做。
解決
請(qǐng)看以下代碼:
package memento import "fmt" type Memento interface{} type Text struct { content string } type textMemento struct { content string } func (t *Text) Write(content string) { t.content = content } func (t *Text) Save() Memento { return &textMemento{ content: t.content, } } func (t *Text) Load(m Memento) { tm := m.(*textMemento) t.content = tm.content } func (t *Text) Show() { fmt.Println("content:", t.content) }
我們定義了textMemento結(jié)構(gòu)體用于保存當(dāng)前快照,并在Load方法中將快照覆蓋到當(dāng)前內(nèi)容。
測(cè)試代碼:
package memento func ExampleText() { text := &Text{ content: "how are you", } text.Show() progress := text.Save() text.Write("fine think you and you") text.Show() text.Load(progress) text.Show() // Output: // content: how are you // content: fine think you and you // content: how are you }
觀察者模式Observer
問(wèn)題
如果你需要在一個(gè)對(duì)象的狀態(tài)被改變時(shí),其他對(duì)象能作為其“觀察者”而被通知,就可以使用觀察者模式。
我們將自身的狀態(tài)改變就會(huì)通知給其他對(duì)象的對(duì)象稱(chēng)為“發(fā)布者”,關(guān)注發(fā)布者狀態(tài)變化的對(duì)象則稱(chēng)為“訂閱者”。
解決
請(qǐng)看以下代碼:
package observer import "fmt" // 發(fā)布者 type Subject struct { observers []Observer content string } func NewSubject() *Subject { return &Subject{ observers: make([]Observer, 0), } } // 添加訂閱者 func (s *Subject) AddObserver(o Observer) { s.observers = append(s.observers, o) } // 改變發(fā)布者的狀態(tài) func (s *Subject) UpdateContext(content string) { s.content = content s.notify() } // 通知訂閱者接口 type Observer interface { Do(*Subject) } func (s *Subject) notify() { for _, o := range s.observers { o.Do(s) } } // 訂閱者 type Reader struct { name string } func NewReader(name string) *Reader { return &Reader{ name: name, } } func (r *Reader) Do(s *Subject) { fmt.Println(r.name + " get " + s.content) }
很簡(jiǎn)單,我們只要實(shí)現(xiàn)一個(gè)通知notify方法,在發(fā)布者的狀態(tài)改變時(shí)執(zhí)行即可。
測(cè)試代碼:
package observer func ExampleObserver() { subject := NewSubject() boy := NewReader("小明") girl := NewReader("小美") subject.AddObserver(boy) subject.AddObserver(girl) subject.UpdateContext("hi~") // Output: // 小明 get hi~ // 小美 get hi~ }
狀態(tài)模式 State
問(wèn)題
如果一個(gè)對(duì)象的實(shí)現(xiàn)方法會(huì)根據(jù)自身的狀態(tài)而改變,就可以使用狀態(tài)模式。
舉個(gè)例子:假設(shè)有一個(gè)開(kāi)門(mén)的方法,門(mén)的狀態(tài)在一開(kāi)始是“關(guān)閉”,你可以執(zhí)行open方法和close方法,當(dāng)你執(zhí)行了open方法,門(mén)的狀態(tài)就變成了“開(kāi)啟”,再執(zhí)行open方法就不會(huì)執(zhí)行開(kāi)門(mén)的功能,而是返回“門(mén)已開(kāi)啟”,如果執(zhí)行close方法,門(mén)的狀態(tài)就變成了“關(guān)閉”,再執(zhí)行close方法就不會(huì)執(zhí)行關(guān)門(mén)的功能,而是返回“門(mén)已關(guān)閉”。這是一個(gè)簡(jiǎn)單的例子,我們將為每個(gè)狀態(tài)提供不同的實(shí)現(xiàn)方法,將這些方法組織起來(lái)很麻煩,如果狀態(tài)也越來(lái)越多呢?無(wú)疑,這將會(huì)使代碼變得臃腫。
解決
如果我們需要為一個(gè)門(mén)對(duì)象提供3種狀態(tài)下的open和close方法:
- “開(kāi)啟”狀態(tài)下,open方法返回“門(mén)已開(kāi)啟”,close方法返回“關(guān)閉成功”。
- “關(guān)閉”狀態(tài)下,open方法返回“開(kāi)啟成功”,close方法返回“門(mén)已關(guān)閉”。
- “損壞”狀態(tài)下,open方法返回“門(mén)已損壞,無(wú)法開(kāi)啟”,close方法返回“門(mén)已損壞,無(wú)法關(guān)閉”。
請(qǐng)看以下代碼:
package state import "fmt" // 不同狀態(tài)需要實(shí)現(xiàn)的接口 type state interface { open(*door) close(*door) } // 門(mén)對(duì)象 type door struct { opened state closed state damaged state currentState state // 當(dāng)前狀態(tài) } func (d *door) open() { d.currentState.open(d) } func (d *door) close() { d.currentState.close(d) } func (d *door) setState(s state) { d.currentState = s } // 開(kāi)啟狀態(tài) type opened struct{} func (o *opened) open(d *door) { fmt.Println("門(mén)已開(kāi)啟") } func (o *opened) close(d *door) { fmt.Println("關(guān)閉成功") } // 關(guān)閉狀態(tài) type closed struct{} func (c *closed) open(d *door) { fmt.Println("開(kāi)啟成功") } func (c *closed) close(d *door) { fmt.Println("門(mén)已關(guān)閉") } // 損壞狀態(tài) type damaged struct{} func (a *damaged) open(d *door) { fmt.Println("門(mén)已損壞,無(wú)法開(kāi)啟") } func (a *damaged) close(d *door) { fmt.Println("門(mén)已損壞,無(wú)法關(guān)閉") }
我們的門(mén)對(duì)象door實(shí)現(xiàn)了open和close方法,在方法中,只需要調(diào)用當(dāng)前狀態(tài)currentState的open和close方法即可。
測(cè)試代碼:
package state func ExampleState() { door := &door{} // 開(kāi)啟狀態(tài) opened := &opened{} door.setState(opened) door.open() door.close() // 關(guān)閉狀態(tài) closed := &closed{} door.setState(closed) door.open() door.close() // 損壞狀態(tài) damaged := &damaged{} door.setState(damaged) door.open() door.close() // Output: // 門(mén)已開(kāi)啟 // 關(guān)閉成功 // 開(kāi)啟成功 // 門(mén)已關(guān)閉 // 門(mén)已損壞,無(wú)法開(kāi)啟 // 門(mén)已損壞,無(wú)法關(guān)閉 }
策略模式Strategy
問(wèn)題
假設(shè)需要實(shí)現(xiàn)一組出行的功能,出現(xiàn)的方案可以選擇步行、騎行、開(kāi)車(chē),最簡(jiǎn)單的做法就是分別實(shí)現(xiàn)這3種方法供客戶(hù)端調(diào)用。但這樣做就使對(duì)象與其代碼實(shí)現(xiàn)變得耦合了,客戶(hù)端需要決定出行方式,然后決定調(diào)用步行出行、騎行出行、開(kāi)車(chē)出行等方法,這不符合開(kāi)閉原則。
而策略模式的區(qū)別在于,它會(huì)將這些出行方案抽取到一組被稱(chēng)為策略的類(lèi)中,客戶(hù)端還是調(diào)用同一個(gè)出行對(duì)象,不需要關(guān)注實(shí)現(xiàn)細(xì)節(jié),只需要在參數(shù)中指定所需的策略即可。
解決
請(qǐng)看以下代碼:
package strategy import "fmt" type Travel struct { name string strategy Strategy } func NewTravel(name string, strategy Strategy) *Travel { return &Travel{ name: name, strategy: strategy, } } func (p *Travel) traffic() { p.strategy.traffic(p) } type Strategy interface { traffic(*Travel) } type Walk struct{} func (w *Walk) traffic(t *Travel) { fmt.Println(t.name + " walk") } type Ride struct{} func (w *Ride) traffic(t *Travel) { fmt.Println(t.name + " ride") } type Drive struct{} func (w *Drive) traffic(t *Travel) { fmt.Println(t.name + " drive") }
我們定義了strategy一組策略接口,為其實(shí)現(xiàn)了Walk、Ride、Drive算法??蛻?hù)端只需要執(zhí)行traffic方法即可,無(wú)需關(guān)注實(shí)現(xiàn)細(xì)節(jié)。
測(cè)試代碼:
package strategy func ExampleTravel() { walk := &Walk{} Travel1 := NewTravel("小明", walk) Travel1.traffic() ride := &Ride{} Travel2 := NewTravel("小美", ride) Travel2.traffic() drive := &Drive{} Travel3 := NewTravel("小剛", drive) Travel3.traffic() // Output: // 小明 walk // 小美 ride // 小剛 drive }
模板方法模式Template Method
問(wèn)題
模板方法模式就是將算法分解為一系列步驟,然后在一個(gè)模版方法中依次調(diào)用這些步驟。這樣客戶(hù)端就不需要了解各個(gè)步驟的實(shí)現(xiàn)細(xì)節(jié),只需要調(diào)用模版即可。
解決
一個(gè)非常簡(jiǎn)單的例子,請(qǐng)看以下代碼:
package templatemethod import "fmt" type PrintTemplate interface { Print(name string) } type template struct { isTemplate PrintTemplate name string } func (t *template) Print() { t.isTemplate.Print(t.name) } type A struct{} func (a *A) Print(name string) { fmt.Println("a: " + name) // 業(yè)務(wù)代碼…… } type B struct{} func (b *B) Print(name string) { fmt.Println("b: " + name) // 業(yè)務(wù)代碼…… }
測(cè)試代碼:
package templatemethod func ExamplePrintTemplate() { templateA := &A{} template := &template{ isTemplate: templateA, name: "hi~", } template.Print() templateB := &B{} template.isTemplate = templateB template.Print() // Output: // a: hi~ // b: hi~ }
訪問(wèn)者模式Visitor
問(wèn)題
訪問(wèn)者模式試圖解決這樣一個(gè)問(wèn)題:在不改變類(lèi)的對(duì)象結(jié)構(gòu)的前提下增加新的操作。
解決
請(qǐng)看以下代碼:
package visitor import "fmt" type Shape interface { accept(visitor) } type square struct{} func (s *square) accept(v visitor) { v.visitForSquare(s) } type circle struct{} func (c *circle) accept(v visitor) { v.visitForCircle(c) } type visitor interface { visitForSquare(*square) visitForCircle(*circle) } type sideCalculator struct{} func (a *sideCalculator) visitForSquare(s *square) { fmt.Println("square side") } func (a *sideCalculator) visitForCircle(s *circle) { fmt.Println("circle side") } type radiusCalculator struct{} func (a *radiusCalculator) visitForSquare(s *square) { fmt.Println("square radius") } func (a *radiusCalculator) visitForCircle(c *circle) { fmt.Println("circle radius") }
測(cè)試代碼:
package visitor func ExampleShape() { square := &square{} circle := &circle{} side := &sideCalculator{} square.accept(side) circle.accept(side) radius := &radiusCalculator{} square.accept(radius) circle.accept(radius) // Output: // square side // circle side // square radius // circle radius }
設(shè)計(jì)模式的“道”
上面那么多種設(shè)計(jì)模式你能記住幾種呢?設(shè)計(jì)模式分為“術(shù)”的部分和“道”的部分,上面那些設(shè)計(jì)模式就是“術(shù)”的部分,他們是一些圍繞著設(shè)計(jì)模式核心思路的經(jīng)典解決方案。換句話(huà)說(shuō),重要的是理解為什么要用那些設(shè)計(jì)模式,具體問(wèn)題,具體分析,而不是把某種設(shè)計(jì)模式生搬硬套進(jìn)代碼。
設(shè)計(jì)模式有6大原則,以上的設(shè)計(jì)模式目的就是為了使軟件系統(tǒng)能達(dá)到這些原則:
開(kāi)閉原則
軟件應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
對(duì)系統(tǒng)進(jìn)行擴(kuò)展,而無(wú)需修改現(xiàn)有的代碼。這可以降低軟件的維護(hù)成本,同時(shí)也增加可擴(kuò)展性。
里氏替換原則
任何基類(lèi)可以出現(xiàn)的地方,子類(lèi)一定可以出現(xiàn)。
里氏替換原則是對(duì)開(kāi)閉原則的補(bǔ)充,實(shí)現(xiàn)開(kāi)閉原則的關(guān)鍵步驟就是抽象化,基類(lèi)與子類(lèi)的關(guān)系就是要盡可能的抽象化。
依賴(lài)倒置原則
面向接口編程,抽象不應(yīng)該依賴(lài)于具體類(lèi),具體類(lèi)應(yīng)當(dāng)依賴(lài)于抽象。
這是為了減少類(lèi)間的耦合,使系統(tǒng)更適宜于擴(kuò)展,也更便于維護(hù)。
單一職責(zé)原則
一個(gè)類(lèi)應(yīng)該只有一個(gè)發(fā)生變化的原因。
一個(gè)類(lèi)承載的越多,耦合度就越高。如果類(lèi)的職責(zé)單一,就可以降低出錯(cuò)的風(fēng)險(xiǎn),也可以提高代碼的可讀性。
最少知道原則
一個(gè)實(shí)體應(yīng)當(dāng)盡量少地與其他實(shí)體之間發(fā)生相互作用。
還是為了降低耦合,一個(gè)類(lèi)與其他類(lèi)的關(guān)聯(lián)越少,越易于擴(kuò)展。
接口分離原則
使用多個(gè)專(zhuān)門(mén)的接口,而不使用高耦合的單一接口。
避免同一個(gè)接口占用過(guò)多的職責(zé),更明確的劃分,可以降低耦合。高耦合會(huì)導(dǎo)致程序不易擴(kuò)展,提高出錯(cuò)的風(fēng)險(xiǎn)。
到此這篇關(guān)于Go語(yǔ)言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用的文章就介紹到這了,更多相關(guān)Go語(yǔ)言 設(shè)計(jì)模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語(yǔ)言beego框架web開(kāi)發(fā)語(yǔ)法筆記示例
這篇文章主要為大家介紹了go語(yǔ)言beego框架web開(kāi)發(fā)語(yǔ)法筆記示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04使用Go開(kāi)發(fā)硬件驅(qū)動(dòng)程序的流程步驟
Golang是一種簡(jiǎn)潔、高效的編程語(yǔ)言,它的強(qiáng)大并發(fā)性能和豐富的標(biāo)準(zhǔn)庫(kù)使得它成為了開(kāi)發(fā)硬件驅(qū)動(dòng)的理想選擇,在本文中,我們將探討如何使用Golang開(kāi)發(fā)硬件驅(qū)動(dòng)程序,并提供一個(gè)實(shí)例來(lái)幫助你入門(mén),需要的朋友可以參考下2023-11-11Go語(yǔ)言實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式的方法總結(jié)
這篇文章主要介紹了在?Go?語(yǔ)言中實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式的多種方法,并重點(diǎn)探討了通道、條件變量的適用場(chǎng)景和優(yōu)缺點(diǎn),需要的可參考一下2023-05-05golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析
這篇文章主要為大家介紹了golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析
這篇文章主要為大家介紹了Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Golang中時(shí)間格式化的實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中進(jìn)行時(shí)間進(jìn)行格式化的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-09-09gin自定義中間件解決requestBody不可重復(fù)讀問(wèn)題(最新推薦)
這篇文章主要介紹了gin自定義中間件解決requestBody不可重復(fù)讀問(wèn)題,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayo
這篇文章主要為大家介紹了Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayout方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08