您好,登錄后才能下訂單哦!
這篇“Go語言怎么實現23種設計模式”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Go語言怎么實現23種設計模式”文章吧。
創建型模式是處理對象創建的設計模式,試圖根據實際情況使用合適的方式創建對象,增加已有代碼的靈活性和可復用性。
假設我們的業務需要一個支付渠道,我們開發了一個Pay方法,其可以用于支付。請看以下示例:
type Pay interface { Pay() string } type PayReq struct { OrderId string // 訂單號 } func (p *PayReq) Pay() string { // todo fmt.Println(p.OrderId) return "支付成功" }
如上,我們定義了接口Pay,并實現了其方法Pay()。
如果業務需求變更,需要我們提供多種支付方式,一種叫APay,一種叫BPay,這二種支付方式所需的參數不同,APay只需要訂單號OrderId,BPay則需要訂單號OrderId和Uid。此時如何修改?
很容易想到的是在原有的代碼基礎上修改,比如:
type Pay interface { APay() string BPay() string } type PayReq struct { OrderId string // 訂單號 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支付成功" }
我們為Pay接口實現了APay() 和BPay() 方法。雖然暫時實現了業務需求,但卻使得結構體PayReq變得冗余了,APay() 并不需要Uid參數。如果之后再增加CPay、DPay、EPay,可想而知,代碼會變得越來越難以維護。
隨著后續業務迭代,將不得不編寫出復雜的代碼。
讓我們想象一個工廠類,這個工廠類需要生產電線和開關等器具,我們可以為工廠類提供一個生產方法,當電線機器調用生產方法時,就產出電線,當開關機器調用生產方法時,就產出開關。
套用到我們的支付業務來,就是我們不再為接口提供APay方法、BPay方法,而只提供一個Pay方法,并將A支付方式和B支付方式的區別下放到子類。
請看示例:
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兩個結構體重寫了Pay() 方法,如果需要添加一種新的支付方式, 只需要重寫新的Pay() 方法即可。
工廠方法的優點就在于避免了創建者和具體產品之間的緊密耦合,從而使得代碼更容易維護。
測試代碼:
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") } }
抽象工廠模式基于工廠方法模式。兩者的區別在于:工廠方法模式是創建出一種產品,而抽象工廠模式是創建出一類產品。這二種都屬于工廠模式,在設計上是相似的。
假設,有一個存儲工廠,提供redis和mysql兩種存儲數據的方式。如果使用工廠方法模式,我們就需要一個存儲工廠,并提供SaveRedis方法和SaveMysql方法。
如果此時業務還需要分成存儲散文和古詩兩種載體,這兩種載體都可以進行redis和mysql存儲。就可以使用抽象工廠模式,我們需要一個存儲工廠作為父工廠,散文工廠和古詩工廠作為子工廠,并提供SaveRedis方法和SaveMysql方法。
以上文的存儲工廠業務為例,用抽象工廠模式的思路來設計代碼,就像下面這樣:
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 古詩 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") }
我們定義了存儲工廠,也就是SaveArticle接口,并實現了CreateProse方法和CreateAncientPoetry方法,這2個方法分別用于創建散文工廠和古詩工廠。
然后我們又分別為散文工廠和古詩工廠實現了SaveProse方法和SaveAncientPoetry方法,并用Redis結構體和Mysql結構體分別重寫了2種存儲方法。
測試代碼:
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 }
假設業務需要按步驟創建一系列復雜的對象,實現這些步驟的代碼加在一起非常繁復,我們可以將這些代碼放進一個包含了眾多參數的構造函數中,但這個構造函數看起來將會非常雜亂無章,且難以維護。
假設業務需要建造一個房子對象,需要先打地基、建墻、建屋頂、建花園、放置家具……。我們需要非常多的步驟,并且這些步驟之間是有聯系的,即使將各個步驟從一個大的構造函數抽出到其他小函數中,整個程序的層次結構看起來依然很復雜。
如何解決呢?像這種復雜的有許多步驟的構造函數,就可以用建造者模式來設計。
建造者模式的用處就在于能夠分步驟創建復雜對象。
在建造者模式中,我們需要清晰的定義每個步驟的代碼,然后在一個構造函數中操作這些步驟,我們需要一個主管類,用這個主管類來管理各步驟。這樣我們就只需要將所需參數傳給一個構造函數,構造函數再將參數傳遞給對應的主管類,最后由主管類完成后續所有建造任務。
請看以下代碼:
package builder import "fmt" // 建造者接口 type Builder interface { Part1() Part2() Part3() } // 管理類 type Director struct { builder Builder } // 構造函數 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") }
如上,我們實現part1、part2、part3這3個步驟,只需要執行構造函數,對應的管理類就可以運行建造方法Construct,完成3個步驟的執行。
測試代碼:
package builder func ExampleBuilder() { builder := &Builder{} director := NewDirector(builder) director.Construct() // Output: // part1 // part2 // part3 }
如果你希望生成一個對象,其與另一個對象完全相同,該如何實現呢?
如果遍歷對象的所有成員,將其依次復制到新對象中,會稍顯麻煩,而且有些對象可能會有私有成員變量遺漏。
原型模式將這個克隆的過程委派給了被克隆的實際對象,被克隆的對象就叫做“原型”。
如果需要克隆一個新的對象,這個對象完全獨立于它的原型,那么就可以使用原型模式。
原型模式的實現非常簡單,請看以下代碼:
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") } }
我們依靠一個Clone方法實現了原型Type1的克隆。
原型模式的用處就在于我們可以克隆對象,而無需與原型對象的依賴相耦合。
存儲著重要對象的全局變量,往往意味著“不安全”,因為你無法保證這個全局變量的值不會在項目的某個引用處被覆蓋掉。
對數據的修改經常導致出乎意料的的結果和難以發現的bug。我在一處更新數據,卻沒有意識到軟件中的另一處期望著完全不同的數據,于是一個功能就失效了,而且找出故障的原因也會非常困難。
一個較好的解決方案是:將這樣的“可變數據”封裝起來,寫一個查詢方法專門用來獲取這些值。
單例模式則更進一步:除了要為“可變數據”提供一個全局訪問方法,它還要保證獲取到的只有同一個實例。也就是說,如果你打算用一個構造函數創建一個對象,單例模式將保證你得到的不是一個新的對象,而是之前創建過的對象,并且每次它所返回的都只有這同一個對象,也就是單例。這可以保護該對象實例不被篡改。
單例模式需要一個全局構造函數,這個構造函數會返回一個私有的對象,無論何時調用,它總是返回相同的對象。
請看以下代碼:
package singleton import ( "sync" ) // 單例實例 type singleton struct { Value int } type Singleton interface { getValue() int } func (s singleton) getValue() int { return s.Value } var ( instance *singleton once sync.Once ) // 構造方法,用于獲取單例模式對象 func GetInstance(v int) Singleton { once.Do(func() { instance = &singleton{Value: v} }) return instance }
單例實例singleton被保存為一個私有的變量,以保證不被其他包的函數引用。
用構造方法GetInstance可以獲得單例實例,函數中使用了sync包的once方法,以保證實例只會在首次調用時被初始化一次,之后再調用構造方法都只會返回同一個實例。
測試代碼:
func TestSingleton(t *testing.T) { ins1 := GetInstance2(1) ins2 := GetInstance2(2) if ins1 != ins2 { t.Fatal("instance is not equal") } }
如果你需要更加嚴格地控制全局變量,這確實很有必要,那么就使用單例模式吧。
結構型模式將一些對象和類組裝成更大的結構體,并同時保持結構的靈活和高效。
適配器模式說白了就是兼容。
假設一開始我們提供了A對象,后期隨著業務迭代,又需要從A對象的基礎之上衍生出不同的需求。如果有很多函數已經在線上調用了A對象,此時再對A對象進行修改就比較麻煩,因為需要考慮兼容問題。還有更糟糕的情況, 你可能沒有程序庫的源代碼, 從而無法對其進行修改。
此時就可以用一個適配器,它就像一個接口轉換器,調用方只需要調用這個適配器接口,而不需要關注其背后的實現,由適配器接口封裝復雜的過程。
假設有2個接口,一個將厘米轉為米,一個將米轉為厘米。我們提供一個適配器接口,使調用方不需要再操心調用哪個接口,直接由適配器做好兼容。
請看以下代碼:
package adapter // 提供一個獲取米的接口和一個獲取厘米的接口 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) }
上面實現了Cm和M兩個接口,并由適配器LengthAdapter做兼容。
測試代碼:
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) } }
假設一開始業務需要兩種發送信息的渠道,sms和email,我們可以分別實現sms和email兩個接口。
之后隨著業務迭代,又產生了新的需求,需要提供兩種系統發送方式,systemA和systemB,并且這兩種系統發送方式都應該支持sms和email渠道。
此時至少需要提供4種方法:systemA to sms,systemA to email,systemB to sms,systemB to email。
如果再分別增加一種渠道和一種系統發送方式,就需要提供9種方法。這將導致代碼的復雜程度指數增長。
其實之前我們是在用繼承的想法來看問題,橋接模式則希望將繼承關系轉變為關聯關系,使兩個類獨立存在。
詳細說一下:
橋接模式需要將抽象和實現區分開;
橋接模式需要將“渠道”和“系統發送方式”這兩種類別區分開;
最后在“系統發送方式”的類里調用“渠道”的抽象接口,使他們從繼承關系轉變為關聯關系。
用一句話總結橋接模式的理念,就是:“將抽象與實現解耦,將不同類別的繼承關系改為關聯關系。 ”
請看以下代碼:
package bridge import "fmt" // 兩種發送消息的方法 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)) } // 兩種發送系統 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二種實現,以及接口SendMessage。接著我們實現了systemA和systemB,并調用了抽象接口SendMessage。
測試代碼:
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 }
如果你想要拆分或重組一個具有多重功能的復雜類,可以使用橋接模式。
在項目中,如果我們需要用到樹狀結構,就可以使用對象樹模式。換言之,如果項目的核心模型不能以樹狀結構表示,則沒必要使用對象樹模式。
對象樹模式的用處就在于可以利用多態和遞歸機制更方便地使用復雜樹結構。
請看以下代碼:
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方法中使用遞歸打印出了整棵樹結構。
測試代碼:
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 }
有時候我們需要在一個類的基礎上擴展另一個類,例如,一個披薩類,你可以在披薩類的基礎上增加番茄披薩類和芝士披薩類。此時就可以使用裝飾模式,簡單來說,裝飾模式就是將對象封裝到另一個對象中,用以為原對象綁定新的行為。
如果你希望在無需修改代碼的情況下使用對象,并且希望為對象新增額外的行為,就可以考慮使用裝飾模式。
用上文的披薩類做例子。請看以下代碼:
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接口,創建了base類,實現了方法getPrice。然后再用裝飾模式的理念,實現了tomatoTopping和cheeseTopping類,他們都封裝了pizza接口的getPrice方法。
測試代碼:
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 }
如果你需要初始化大量復雜的庫或框架,就需要管理其依賴關系并且按正確的順序執行。此時就可以用一個外觀類來統一處理這些依賴關系,以對其進行整合。
外觀模式和建造者模式很相似。兩者的區別在于,外觀模式是一種結構型模式,她的目的是將對象組合起來,而不是像建造者模式那樣創建出不同的產品。
請看以下代碼:
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" } // 外觀類 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) }
假設要初始化APIA和APIB,我們就可以通過一個外觀類API進行處理,在外觀類接口Test方法中分別執行類TestA方法和TestB方法。
測試代碼:
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) } }
在一些情況下,程序沒有足夠的內存容量支持存儲大量對象,或者大量的對象存儲著重復的狀態,此時就會造成內存資源的浪費。
享元模式提出了這樣的解決方案:如果多個對象中相同的狀態可以共用,就能在在有限的內存容量中載入更多對象。
如上所說,享元模式希望抽取出能在多個對象間共享的重復狀態。
我們可以使用map結構來實現這一設想,假設需要存儲一些代表顏色的對象,使用享元模式可以這樣做,請看以下代碼:
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 } // 存儲color對象 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, } }
我們定義了一個享元工廠,使用map存儲相同對象(key)的狀態(value)。這個享元工廠可以使我們更方便和安全的訪問各種享元,保證其狀態不被修改。
我們定義了NewColorViewer方法,它會調用享元工廠的Get方法存儲對象,而在享元工廠的實現中可以看到,相同狀態的對象只會占用一次。
測試代碼:
package flyweight import "testing" func TestFlyweight(t *testing.T) { viewer1 := NewColorViewer("blue") viewer2 := NewColorViewer("blue") if viewer1.ColorFlyweight != viewer2.ColorFlyweight { t.Fail() } }
當程序需要存儲大量對象且沒有足夠的內存容量時,可以考慮使用享元模式。
如果你需要在訪問一個對象時,有一個像“代理”一樣的角色,她可以在訪問對象之前為你進行緩存檢查、權限判斷等訪問控制,在訪問對象之后為你進行結果緩存、日志記錄等結果處理,那么就可以考慮使用代理模式。
回憶一下一些web框架的router模塊,當客戶端訪問一個接口時,在最終執行對應的接口之前,router模塊會執行一些事前操作,進行權限判斷等操作,在執行之后還會記錄日志,這就是典型的代理模式。
代理模式需要一個代理類,其包含執行真實對象所需的成員變量,并由代理類管理整個生命周期。
請看以下代碼:
package proxy import "fmt" type Subject interface { Proxy() string } // 代理 type Proxy struct { real RealSubject } func (p Proxy) Proxy() string { var res string // 在調用真實對象之前,檢查緩存,判斷權限,等等 p.real.Pre() // 調用真實對象 p.real.Real() // 調用之后的操作,如緩存結果,對結果進行處理,等等 p.real.After() return res } // 真實對象 type RealSubject struct{} func (RealSubject) Real() { fmt.Print("real") } func (RealSubject) Pre() { fmt.Print("pre:") } func (RealSubject) After() { fmt.Print(":after") }
我們定義了代理類Proxy,執行Proxy之后,在調用真實對象Real之前,我們會先調用事前對象Pre,并在執行真實對象Real之后,調用事后對象After。
測試代碼:
package proxy func ExampleProxy() { var sub Subject sub = &Proxy{} sub.Proxy() // Output: // pre:real:after }
行為型模式處理對象和類之間的通信,并使其保持高效的溝通和委派。
假設我們要讓程序按照指定的步驟執行,并且這個步驟的順序不是固定的,而是可以根據不同需求改變的,每個步驟都會對請求進行一些處理,并將結果傳遞給下一個步驟的處理者,就像一條流水線一樣,我們該如何實現?
當遇到這種必須按順序執行多個處理者,并且處理者的順序可以改變的需求,我們可以考慮使用責任鏈模式。
責任鏈模式使用了類似鏈表的結構。請看以下代碼:
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 }
我們實現了方法execute和setNext,并定義了aPart、bPart、endPart這3個處理者,每個處理者都可以通過execute方法執行其對應的業務代碼,并可以通過setNext方法決定下一個處理者是誰。除了endPart是最終的處理者之外,在它之前的處理者aPart、bPart的順序都可以任意調整。
請看以下測試代碼:
func ExampleChain() { startPart := &endPart{} aPart := &aPart{} aPart.setNext(startPart) bPart := &bPart{} bPart.setNext(aPart) do := &Do{} bPart.execute(do) // Output: // bPart // aPart // endPart }
我們也可以調整處理者的執行順序:
func ExampleChain2() { startPart := &endPart{} bPart := &bPart{} bPart.setNext(startPart) aPart := &aPart{} aPart.setNext(bPart) do := &Do{} aPart.execute(do) // Output: // aPart // bPart // endPart }
假設你實現了開啟和關閉電視機的功能,隨著業務迭代,還需要實現開啟和關閉冰箱的功能,開啟和關閉電燈的功能,開啟和關閉微波爐的功能……這些功能都基于你的基類,開啟和關閉。如果你之后對基類進行修改,很可能會影響到其他功能,這使項目變得不穩定了。
一個優秀的設計往往會關注于軟件的分層與解耦,命令模式試圖做到這樣的結果:讓命令和對應功能解耦,并能根據不同的請求將其方法參數化。
還是用開啟和關閉家用電器的例子來舉例吧。請看以下代碼:
package command import "fmt" // 請求者 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") }
我們分別實現了請求者button,命令接口command,接收者device。請求者button就像是那個可以執行開啟或關閉的遙控器,命令接口command則是一個中間層,它使我們的請求者和接收者解藕。
測試代碼:
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() }
迭代器模式用于遍歷集合中的元素,無論集合的數據結構是怎樣的。
請看以下代碼:
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 }
測試代碼:
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} }
中介者模式試圖解決網狀關系的復雜關聯,降低對象間的耦合度。
舉個例子,假設一個十字路口上的車都是對象,它們會執行不同的操作,前往不同的目的地,那么在十字路口指揮的交警就是“中介者”。
各個對象通過執行中介者接口,再由中介者維護對象之間的聯系。這能使對象變得更獨立,比較適合用在一些對象是網狀關系的案例上。
假設有p1,p2,p3這3個發送者,p1 發送的消息p2能收到,p2 發送的消息p1能收到,p3 發送的消息則p1和p2能收到,如何實現呢?像這種情況就很適合用中介者模式實現。
請看以下代碼:
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個對象,然后實現了中介者sendMessage。
測試代碼:
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 }
常用的文字編輯器都支持保存和恢復一段文字的操作,如果我們想要在程序中實現保存和恢復的功能該怎么做呢?
我們需要提供保存和恢復的功能,當保存功能被調用時,就會生成當前對象的快照,在恢復功能被調用時,就會用之前保存的快照覆蓋當前的快照。這可以使用備忘錄模式來做。
請看以下代碼:
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結構體用于保存當前快照,并在Load方法中將快照覆蓋到當前內容。
測試代碼:
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 }
如果你需要在一個對象的狀態被改變時,其他對象能作為其“觀察者”而被通知,就可以使用觀察者模式。
我們將自身的狀態改變就會通知給其他對象的對象稱為“發布者”,關注發布者狀態變化的對象則稱為“訂閱者”。
請看以下代碼:
package observer import "fmt" // 發布者 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) } // 改變發布者的狀態 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) }
很簡單,我們只要實現一個通知notify方法,在發布者的狀態改變時執行即可。
測試代碼:
package observer func ExampleObserver() { subject := NewSubject() boy := NewReader("小明") girl := NewReader("小美") subject.AddObserver(boy) subject.AddObserver(girl) subject.UpdateContext("hi~") // Output: // 小明 get hi~ // 小美 get hi~ }
如果一個對象的實現方法會根據自身的狀態而改變,就可以使用狀態模式。
舉個例子:假設有一個開門的方法,門的狀態在一開始是“關閉”,你可以執行open方法和close方法,當你執行了open方法,門的狀態就變成了“開啟”,再執行open方法就不會執行開門的功能,而是返回“門已開啟”,如果執行close方法,門的狀態就變成了“關閉”,再執行close方法就不會執行關門的功能,而是返回“門已關閉”。這是一個簡單的例子,我們將為每個狀態提供不同的實現方法,將這些方法組織起來很麻煩,如果狀態也越來越多呢?無疑,這將會使代碼變得臃腫。
如果我們需要為一個門對象提供3種狀態下的open和close方法:
“開啟”狀態下,open方法返回“門已開啟”,close方法返回“關閉成功”。
“關閉”狀態下,open方法返回“開啟成功”,close方法返回“門已關閉”。
“損壞”狀態下,open方法返回“門已損壞,無法開啟”,close方法返回“門已損壞,無法關閉”。
請看以下代碼:
package state import "fmt" // 不同狀態需要實現的接口 type state interface { open(*door) close(*door) } // 門對象 type door struct { opened state closed state damaged state currentState state // 當前狀態 } 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 } // 開啟狀態 type opened struct{} func (o *opened) open(d *door) { fmt.Println("門已開啟") } func (o *opened) close(d *door) { fmt.Println("關閉成功") } // 關閉狀態 type closed struct{} func (c *closed) open(d *door) { fmt.Println("開啟成功") } func (c *closed) close(d *door) { fmt.Println("門已關閉") } // 損壞狀態 type damaged struct{} func (a *damaged) open(d *door) { fmt.Println("門已損壞,無法開啟") } func (a *damaged) close(d *door) { fmt.Println("門已損壞,無法關閉") }
我們的門對象door實現了open和close方法,在方法中,只需要調用當前狀態currentState的open和close方法即可。
測試代碼:
package state func ExampleState() { door := &door{} // 開啟狀態 opened := &opened{} door.setState(opened) door.open() door.close() // 關閉狀態 closed := &closed{} door.setState(closed) door.open() door.close() // 損壞狀態 damaged := &damaged{} door.setState(damaged) door.open() door.close() // Output: // 門已開啟 // 關閉成功 // 開啟成功 // 門已關閉 // 門已損壞,無法開啟 // 門已損壞,無法關閉 }
假設需要實現一組出行的功能,出現的方案可以選擇步行、騎行、開車,最簡單的做法就是分別實現這3種方法供客戶端調用。但這樣做就使對象與其代碼實現變得耦合了,客戶端需要決定出行方式,然后決定調用步行出行、騎行出行、開車出行等方法,這不符合開閉原則。
而策略模式的區別在于,它會將這些出行方案抽取到一組被稱為策略的類中,客戶端還是調用同一個出行對象,不需要關注實現細節,只需要在參數中指定所需的策略即可。
請看以下代碼:
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一組策略接口,為其實現了Walk、Ride、Drive算法。客戶端只需要執行traffic方法即可,無需關注實現細節。
測試代碼:
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 }
模板方法模式就是將算法分解為一系列步驟,然后在一個模版方法中依次調用這些步驟。這樣客戶端就不需要了解各個步驟的實現細節,只需要調用模版即可。
一個非常簡單的例子,請看以下代碼:
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) // 業務代碼…… } type B struct{} func (b *B) Print(name string) { fmt.Println("b: " + name) // 業務代碼…… }
測試代碼:
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~ }
訪問者模式試圖解決這樣一個問題:在不改變類的對象結構的前提下增加新的操作。
請看以下代碼:
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") }
測試代碼:
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 }
上面那么多種設計模式你能記住幾種呢?設計模式分為“術”的部分和“道”的部分,上面那些設計模式就是“術”的部分,他們是一些圍繞著設計模式核心思路的經典解決方案。換句話說,重要的是理解為什么要用那些設計模式,具體問題,具體分析,而不是把某種設計模式生搬硬套進代碼。
設計模式有6大原則,以上的設計模式目的就是為了使軟件系統能達到這些原則:
軟件應該對擴展開放,對修改關閉。
對系統進行擴展,而無需修改現有的代碼。這可以降低軟件的維護成本,同時也增加可擴展性。
任何基類可以出現的地方,子類一定可以出現。
里氏替換原則是對開閉原則的補充,實現開閉原則的關鍵步驟就是抽象化,基類與子類的關系就是要盡可能的抽象化。
面向接口編程,抽象不應該依賴于具體類,具體類應當依賴于抽象。
這是為了減少類間的耦合,使系統更適宜于擴展,也更便于維護。
一個類應該只有一個發生變化的原因。
一個類承載的越多,耦合度就越高。如果類的職責單一,就可以降低出錯的風險,也可以提高代碼的可讀性。
一個實體應當盡量少地與其他實體之間發生相互作用。
還是為了降低耦合,一個類與其他類的關聯越少,越易于擴展。
使用多個專門的接口,而不使用高耦合的單一接口。
避免同一個接口占用過多的職責,更明確的劃分,可以降低耦合。高耦合會導致程序不易擴展,提高出錯的風險。
以上就是關于“Go語言怎么實現23種設計模式”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。