Go語言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用
設(shè)計(jì)模式是軟件工程中各種常見問題的經(jīng)典解決方案,設(shè)計(jì)模式不只是代碼,而是組織代碼的方式。假設(shè)一行行的代碼是磚,設(shè)計(jì)模式就是藍(lán)圖。
創(chuàng)建型模式
創(chuàng)建型模式是處理對象創(chuàng)建的設(shè)計(jì)模式,試圖根據(jù)實(shí)際情況使用合適的方式創(chuàng)建對象,增加已有代碼的靈活性和可復(fù)用性。
工廠方法模式 Factory Method
問題
假設(shè)我們的業(yè)務(wù)需要一個(gè)支付渠道,我們開發(fā)了一個(gè)Pay方法,其可以用于支付。請看以下示例:
type Pay interface {
Pay() string
}
type PayReq struct {
OrderId string // 訂單號
}
func (p *PayReq) Pay() string {
// todo
fmt.Println(p.OrderId)
return "支付成功"
}如上,我們定義了接口Pay,并實(shí)現(xiàn)了其方法Pay()。
如果業(yè)務(wù)需求變更,需要我們提供多種支付方式,一種叫APay,一種叫BPay,這二種支付方式所需的參數(shù)不同,APay只需要訂單號OrderId,BPay則需要訂單號OrderId和Uid。此時(shí)如何修改?
很容易想到的是在原有的代碼基礎(chǔ)上修改,比如:
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支付成功"
}我們?yōu)镻ay接口實(shí)現(xiàn)了APay() 和BPay() 方法。雖然暫時(shí)實(shí)現(xiàn)了業(yè)務(wù)需求,但卻使得結(jié)構(gòu)體PayReq變得冗余了,APay() 并不需要Uid參數(shù)。如果之后再增加CPay、DPay、EPay,可想而知,代碼會變得越來越難以維護(hù)。
隨著后續(xù)業(yè)務(wù)迭代,將不得不編寫出復(fù)雜的代碼。
解決
讓我們想象一個(gè)工廠類,這個(gè)工廠類需要生產(chǎn)電線和開關(guān)等器具,我們可以為工廠類提供一個(gè)生產(chǎn)方法,當(dāng)電線機(jī)器調(diào)用生產(chǎn)方法時(shí),就產(chǎn)出電線,當(dāng)開關(guān)機(jī)器調(diào)用生產(chǎn)方法時(shí),就產(chǎn)出開關(guān)。
套用到我們的支付業(yè)務(wù)來,就是我們不再為接口提供APay方法、BPay方法,而只提供一個(gè)Pay方法,并將A支付方式和B支付方式的區(qū)別下放到子類。
請看示例:
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)體重寫了Pay() 方法,如果需要添加一種新的支付方式, 只需要重寫新的Pay() 方法即可。
工廠方法的優(yōu)點(diǎn)就在于避免了創(chuàng)建者和具體產(chǎn)品之間的緊密耦合,從而使得代碼更容易維護(hù)。
測試代碼:
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
問題
抽象工廠模式基于工廠方法模式。兩者的區(qū)別在于:工廠方法模式是創(chuàng)建出一種產(chǎn)品,而抽象工廠模式是創(chuàng)建出一類產(chǎn)品。這二種都屬于工廠模式,在設(shè)計(jì)上是相似的。
假設(shè),有一個(gè)存儲工廠,提供redis和mysql兩種存儲數(shù)據(jù)的方式。如果使用工廠方法模式,我們就需要一個(gè)存儲工廠,并提供SaveRedis方法和SaveMysql方法。
如果此時(shí)業(yè)務(wù)還需要分成存儲散文和古詩兩種載體,這兩種載體都可以進(jìn)行redis和mysql存儲。就可以使用抽象工廠模式,我們需要一個(gè)存儲工廠作為父工廠,散文工廠和古詩工廠作為子工廠,并提供SaveRedis方法和SaveMysql方法。
解決
以上文的存儲工廠業(yè)務(wù)為例,用抽象工廠模式的思路來設(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 古詩
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接口,并實(shí)現(xiàn)了CreateProse方法和CreateAncientPoetry方法,這2個(gè)方法分別用于創(chuàng)建散文工廠和古詩工廠。
然后我們又分別為散文工廠和古詩工廠實(shí)現(xiàn)了SaveProse方法和SaveAncientPoetry方法,并用Redis結(jié)構(gòu)體和Mysql結(jié)構(gòu)體分別重寫了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
}建造者模式 Builder
問題
假設(shè)業(yè)務(wù)需要按步驟創(chuàng)建一系列復(fù)雜的對象,實(shí)現(xiàn)這些步驟的代碼加在一起非常繁復(fù),我們可以將這些代碼放進(jìn)一個(gè)包含了眾多參數(shù)的構(gòu)造函數(shù)中,但這個(gè)構(gòu)造函數(shù)看起來將會非常雜亂無章,且難以維護(hù)。
假設(shè)業(yè)務(wù)需要建造一個(gè)房子對象,需要先打地基、建墻、建屋頂、建花園、放置家具……。我們需要非常多的步驟,并且這些步驟之間是有聯(lián)系的,即使將各個(gè)步驟從一個(gè)大的構(gòu)造函數(shù)抽出到其他小函數(shù)中,整個(gè)程序的層次結(jié)構(gòu)看起來依然很復(fù)雜。
如何解決呢?像這種復(fù)雜的有許多步驟的構(gòu)造函數(shù),就可以用建造者模式來設(shè)計(jì)。
建造者模式的用處就在于能夠分步驟創(chuàng)建復(fù)雜對象。
解決
在建造者模式中,我們需要清晰的定義每個(gè)步驟的代碼,然后在一個(gè)構(gòu)造函數(shù)中操作這些步驟,我們需要一個(gè)主管類,用這個(gè)主管類來管理各步驟。這樣我們就只需要將所需參數(shù)傳給一個(gè)構(gòu)造函數(shù),構(gòu)造函數(shù)再將參數(shù)傳遞給對應(yīng)的主管類,最后由主管類完成后續(xù)所有建造任務(wù)。
請看以下代碼:
package builder
import "fmt"
// 建造者接口
type Builder interface {
Part1()
Part2()
Part3()
}
// 管理類
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ù),對應(yīng)的管理類就可以運(yùn)行建造方法Construct,完成3個(gè)步驟的執(zhí)行。
測試代碼:
package builder
func ExampleBuilder() {
builder := &Builder{}
director := NewDirector(builder)
director.Construct()
// Output:
// part1
// part2
// part3
}原型模式 Prototype
問題
如果你希望生成一個(gè)對象,其與另一個(gè)對象完全相同,該如何實(shí)現(xiàn)呢?
如果遍歷對象的所有成員,將其依次復(fù)制到新對象中,會稍顯麻煩,而且有些對象可能會有私有成員變量遺漏。
原型模式將這個(gè)克隆的過程委派給了被克隆的實(shí)際對象,被克隆的對象就叫做“原型”。
解決
如果需要克隆一個(gè)新的對象,這個(gè)對象完全獨(dú)立于它的原型,那么就可以使用原型模式。
原型模式的實(shí)現(xiàn)非常簡單,請看以下代碼:
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的克隆。
原型模式的用處就在于我們可以克隆對象,而無需與原型對象的依賴相耦合。
單例模式 Singleton
問題
存儲著重要對象的全局變量,往往意味著“不安全”,因?yàn)槟銦o法保證這個(gè)全局變量的值不會在項(xiàng)目的某個(gè)引用處被覆蓋掉。
對數(shù)據(jù)的修改經(jīng)常導(dǎo)致出乎意料的的結(jié)果和難以發(fā)現(xiàn)的bug。我在一處更新數(shù)據(jù),卻沒有意識到軟件中的另一處期望著完全不同的數(shù)據(jù),于是一個(gè)功能就失效了,而且找出故障的原因也會非常困難。
一個(gè)較好的解決方案是:將這樣的“可變數(shù)據(jù)”封裝起來,寫一個(gè)查詢方法專門用來獲取這些值。
單例模式則更進(jìn)一步:除了要為“可變數(shù)據(jù)”提供一個(gè)全局訪問方法,它還要保證獲取到的只有同一個(gè)實(shí)例。也就是說,如果你打算用一個(gè)構(gòu)造函數(shù)創(chuàng)建一個(gè)對象,單例模式將保證你得到的不是一個(gè)新的對象,而是之前創(chuàng)建過的對象,并且每次它所返回的都只有這同一個(gè)對象,也就是單例。這可以保護(hù)該對象實(shí)例不被篡改。
解決
單例模式需要一個(gè)全局構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)會返回一個(gè)私有的對象,無論何時(shí)調(diào)用,它總是返回相同的對象。
請看以下代碼:
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)造方法,用于獲取單例模式對象
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í)例只會在首次調(diào)用時(shí)被初始化一次,之后再調(diào)用構(gòu)造方法都只會返回同一個(gè)實(shí)例。
測試代碼:
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)型模式將一些對象和類組裝成更大的結(jié)構(gòu)體,并同時(shí)保持結(jié)構(gòu)的靈活和高效。
適配器模式 Adapter
問題
適配器模式說白了就是兼容。
假設(shè)一開始我們提供了A對象,后期隨著業(yè)務(wù)迭代,又需要從A對象的基礎(chǔ)之上衍生出不同的需求。如果有很多函數(shù)已經(jīng)在線上調(diào)用了A對象,此時(shí)再對A對象進(jìn)行修改就比較麻煩,因?yàn)樾枰紤]兼容問題。還有更糟糕的情況, 你可能沒有程序庫的源代碼, 從而無法對其進(jìn)行修改。
此時(shí)就可以用一個(gè)適配器,它就像一個(gè)接口轉(zhuǎn)換器,調(diào)用方只需要調(diào)用這個(gè)適配器接口,而不需要關(guān)注其背后的實(shí)現(xiàn),由適配器接口封裝復(fù)雜的過程。
解決
假設(shè)有2個(gè)接口,一個(gè)將厘米轉(zhuǎn)為米,一個(gè)將米轉(zhuǎn)為厘米。我們提供一個(gè)適配器接口,使調(diào)用方不需要再操心調(diào)用哪個(gè)接口,直接由適配器做好兼容。
請看以下代碼:
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做兼容。
測試代碼:
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
問題
假設(shè)一開始業(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ù)增長。
解決
其實(shí)之前我們是在用繼承的想法來看問題,橋接模式則希望將繼承關(guān)系轉(zhuǎn)變?yōu)殛P(guān)聯(lián)關(guān)系,使兩個(gè)類獨(dú)立存在。
詳細(xì)說一下:
- 橋接模式需要將抽象和實(shí)現(xiàn)區(qū)分開;
- 橋接模式需要將“渠道”和“系統(tǒng)發(fā)送方式”這兩種類別區(qū)分開;
- 最后在“系統(tǒng)發(fā)送方式”的類里調(diào)用“渠道”的抽象接口,使他們從繼承關(guān)系轉(zhuǎn)變?yōu)殛P(guān)聯(lián)關(guān)系。
用一句話總結(jié)橋接模式的理念,就是:“將抽象與實(shí)現(xiàn)解耦,將不同類別的繼承關(guān)系改為關(guān)聯(lián)關(guān)系。 ”
請看以下代碼:
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。
測試代碼:
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ù)雜類,可以使用橋接模式。
對象樹模式Object Tree
問題
在項(xiàng)目中,如果我們需要用到樹狀結(jié)構(gòu),就可以使用對象樹模式。換言之,如果項(xiàng)目的核心模型不能以樹狀結(jié)構(gòu)表示,則沒必要使用對象樹模式。
對象樹模式的用處就在于可以利用多態(tài)和遞歸機(jī)制更方便地使用復(fù)雜樹結(jié)構(gòu)。
解決
請看以下代碼:
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方法中使用遞歸打印出了整棵樹結(jié)構(gòu)。
測試代碼:
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
問題
有時(shí)候我們需要在一個(gè)類的基礎(chǔ)上擴(kuò)展另一個(gè)類,例如,一個(gè)披薩類,你可以在披薩類的基礎(chǔ)上增加番茄披薩類和芝士披薩類。此時(shí)就可以使用裝飾模式,簡單來說,裝飾模式就是將對象封裝到另一個(gè)對象中,用以為原對象綁定新的行為。
如果你希望在無需修改代碼的情況下使用對象,并且希望為對象新增額外的行為,就可以考慮使用裝飾模式。
解決
用上文的披薩類做例子。請看以下代碼:
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類,實(shí)現(xiàn)了方法getPrice。然后再用裝飾模式的理念,實(shí)現(xiàn)了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
}外觀模式Facade
問題
如果你需要初始化大量復(fù)雜的庫或框架,就需要管理其依賴關(guān)系并且按正確的順序執(zhí)行。此時(shí)就可以用一個(gè)外觀類來統(tǒng)一處理這些依賴關(guān)系,以對其進(jìn)行整合。
解決
外觀模式和建造者模式很相似。兩者的區(qū)別在于,外觀模式是一種結(jié)構(gòu)型模式,她的目的是將對象組合起來,而不是像建造者模式那樣創(chuàng)建出不同的產(chǎn)品。
請看以下代碼:
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)
}假設(shè)要初始化APIA和APIB,我們就可以通過一個(gè)外觀類API進(jìn)行處理,在外觀類接口Test方法中分別執(zhí)行類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)
}
}享元模式 Flyweight
問題
在一些情況下,程序沒有足夠的內(nèi)存容量支持存儲大量對象,或者大量的對象存儲著重復(fù)的狀態(tài),此時(shí)就會造成內(nèi)存資源的浪費(fèi)。
享元模式提出了這樣的解決方案:如果多個(gè)對象中相同的狀態(tài)可以共用,就能在在有限的內(nèi)存容量中載入更多對象。
解決
如上所說,享元模式希望抽取出能在多個(gè)對象間共享的重復(fù)狀態(tài)。
我們可以使用map結(jié)構(gòu)來實(shí)現(xiàn)這一設(shè)想,假設(shè)需要存儲一些代表顏色的對象,使用享元模式可以這樣做,請看以下代碼:
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,
}
}我們定義了一個(gè)享元工廠,使用map存儲相同對象(key)的狀態(tài)(value)。這個(gè)享元工廠可以使我們更方便和安全的訪問各種享元,保證其狀態(tài)不被修改。
我們定義了NewColorViewer方法,它會調(diào)用享元工廠的Get方法存儲對象,而在享元工廠的實(shí)現(xiàn)中可以看到,相同狀態(tài)的對象只會占用一次。
測試代碼:
package flyweight
import "testing"
func TestFlyweight(t *testing.T) {
viewer1 := NewColorViewer("blue")
viewer2 := NewColorViewer("blue")
if viewer1.ColorFlyweight != viewer2.ColorFlyweight {
t.Fail()
}
}當(dāng)程序需要存儲大量對象且沒有足夠的內(nèi)存容量時(shí),可以考慮使用享元模式。
代理模式Proxy
問題
如果你需要在訪問一個(gè)對象時(shí),有一個(gè)像“代理”一樣的角色,她可以在訪問對象之前為你進(jìn)行緩存檢查、權(quán)限判斷等訪問控制,在訪問對象之后為你進(jìn)行結(jié)果緩存、日志記錄等結(jié)果處理,那么就可以考慮使用代理模式。
回憶一下一些web框架的router模塊,當(dāng)客戶端訪問一個(gè)接口時(shí),在最終執(zhí)行對應(yīng)的接口之前,router模塊會執(zhí)行一些事前操作,進(jìn)行權(quán)限判斷等操作,在執(zhí)行之后還會記錄日志,這就是典型的代理模式。
解決
代理模式需要一個(gè)代理類,其包含執(zhí)行真實(shí)對象所需的成員變量,并由代理類管理整個(gè)生命周期。
請看以下代碼:
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í)對象之前,檢查緩存,判斷權(quán)限,等等
p.real.Pre()
// 調(diào)用真實(shí)對象
p.real.Real()
// 調(diào)用之后的操作,如緩存結(jié)果,對結(jié)果進(jìn)行處理,等等
p.real.After()
return res
}
// 真實(shí)對象
type RealSubject struct{}
func (RealSubject) Real() {
fmt.Print("real")
}
func (RealSubject) Pre() {
fmt.Print("pre:")
}
func (RealSubject) After() {
fmt.Print(":after")
}我們定義了代理類Proxy,執(zhí)行Proxy之后,在調(diào)用真實(shí)對象Real之前,我們會先調(diào)用事前對象Pre,并在執(zhí)行真實(shí)對象Real之后,調(diào)用事后對象After。
測試代碼:
package proxy
func ExampleProxy() {
var sub Subject
sub = &Proxy{}
sub.Proxy()
// Output:
// pre:real:after
}行為型模式
行為型模式處理對象和類之間的通信,并使其保持高效的溝通和委派。
責(zé)任鏈模式Chain of Responsibility
問題
假設(shè)我們要讓程序按照指定的步驟執(zhí)行,并且這個(gè)步驟的順序不是固定的,而是可以根據(jù)不同需求改變的,每個(gè)步驟都會對請求進(jìn)行一些處理,并將結(jié)果傳遞給下一個(gè)步驟的處理者,就像一條流水線一樣,我們該如何實(shí)現(xiàn)?
當(dāng)遇到這種必須按順序執(zhí)行多個(gè)處理者,并且處理者的順序可以改變的需求,我們可以考慮使用責(zé)任鏈模式。
解決
責(zé)任鏈模式使用了類似鏈表的結(jié)構(gòu)。請看以下代碼:
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è)處理者都可以通過execute方法執(zhí)行其對應(yīng)的業(yè)務(wù)代碼,并可以通過setNext方法決定下一個(gè)處理者是誰。除了endPart是最終的處理者之外,在它之前的處理者aPart、bPart的順序都可以任意調(diào)整。
請看以下測試代碼:
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
問題
假設(shè)你實(shí)現(xiàn)了開啟和關(guān)閉電視機(jī)的功能,隨著業(yè)務(wù)迭代,還需要實(shí)現(xiàn)開啟和關(guān)閉冰箱的功能,開啟和關(guān)閉電燈的功能,開啟和關(guān)閉微波爐的功能……這些功能都基于你的基類,開啟和關(guān)閉。如果你之后對基類進(jìn)行修改,很可能會影響到其他功能,這使項(xiàng)目變得不穩(wěn)定了。
一個(gè)優(yōu)秀的設(shè)計(jì)往往會關(guān)注于軟件的分層與解耦,命令模式試圖做到這樣的結(jié)果:讓命令和對應(yīng)功能解耦,并能根據(jù)不同的請求將其方法參數(shù)化。
解決
還是用開啟和關(guān)閉家用電器的例子來舉例吧。請看以下代碼:
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")
}我們分別實(shí)現(xiàn)了請求者button,命令接口command,接收者device。請求者button就像是那個(gè)可以執(zhí)行開啟或關(guān)閉的遙控器,命令接口command則是一個(gè)中間層,它使我們的請求者和接收者解藕。
測試代碼:
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
問題
迭代器模式用于遍歷集合中的元素,無論集合的數(shù)據(jù)結(jié)構(gòu)是怎樣的。
解決
請看以下代碼:
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}
}中介者模式Mediator
問題
中介者模式試圖解決網(wǎng)狀關(guān)系的復(fù)雜關(guān)聯(lián),降低對象間的耦合度。
舉個(gè)例子,假設(shè)一個(gè)十字路口上的車都是對象,它們會執(zhí)行不同的操作,前往不同的目的地,那么在十字路口指揮的交警就是“中介者”。
各個(gè)對象通過執(zhí)行中介者接口,再由中介者維護(hù)對象之間的聯(lián)系。這能使對象變得更獨(dú)立,比較適合用在一些對象是網(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)。
請看以下代碼:
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è)對象,然后實(shí)現(xiàn)了中介者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
}備忘錄模式Memento
問題
常用的文字編輯器都支持保存和恢復(fù)一段文字的操作,如果我們想要在程序中實(shí)現(xiàn)保存和恢復(fù)的功能該怎么做呢?
我們需要提供保存和恢復(fù)的功能,當(dāng)保存功能被調(diào)用時(shí),就會生成當(dāng)前對象的快照,在恢復(fù)功能被調(diào)用時(shí),就會用之前保存的快照覆蓋當(dā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)容。
測試代碼:
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
問題
如果你需要在一個(gè)對象的狀態(tài)被改變時(shí),其他對象能作為其“觀察者”而被通知,就可以使用觀察者模式。
我們將自身的狀態(tài)改變就會通知給其他對象的對象稱為“發(fā)布者”,關(guān)注發(fā)布者狀態(tài)變化的對象則稱為“訂閱者”。
解決
請看以下代碼:
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)
}很簡單,我們只要實(shí)現(xiàn)一個(gè)通知notify方法,在發(fā)布者的狀態(tài)改變時(shí)執(zhí)行即可。
測試代碼:
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
問題
如果一個(gè)對象的實(shí)現(xiàn)方法會根據(jù)自身的狀態(tài)而改變,就可以使用狀態(tài)模式。
舉個(gè)例子:假設(shè)有一個(gè)開門的方法,門的狀態(tài)在一開始是“關(guān)閉”,你可以執(zhí)行open方法和close方法,當(dāng)你執(zhí)行了open方法,門的狀態(tài)就變成了“開啟”,再執(zhí)行open方法就不會執(zhí)行開門的功能,而是返回“門已開啟”,如果執(zhí)行close方法,門的狀態(tài)就變成了“關(guān)閉”,再執(zhí)行close方法就不會執(zhí)行關(guān)門的功能,而是返回“門已關(guān)閉”。這是一個(gè)簡單的例子,我們將為每個(gè)狀態(tài)提供不同的實(shí)現(xiàn)方法,將這些方法組織起來很麻煩,如果狀態(tài)也越來越多呢?無疑,這將會使代碼變得臃腫。
解決
如果我們需要為一個(gè)門對象提供3種狀態(tài)下的open和close方法:
- “開啟”狀態(tài)下,open方法返回“門已開啟”,close方法返回“關(guān)閉成功”。
- “關(guān)閉”狀態(tài)下,open方法返回“開啟成功”,close方法返回“門已關(guān)閉”。
- “損壞”狀態(tài)下,open方法返回“門已損壞,無法開啟”,close方法返回“門已損壞,無法關(guān)閉”。
請看以下代碼:
package state
import "fmt"
// 不同狀態(tài)需要實(shí)現(xiàn)的接口
type state interface {
open(*door)
close(*door)
}
// 門對象
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
}
// 開啟狀態(tài)
type opened struct{}
func (o *opened) open(d *door) {
fmt.Println("門已開啟")
}
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("開啟成功")
}
func (c *closed) close(d *door) {
fmt.Println("門已關(guān)閉")
}
// 損壞狀態(tài)
type damaged struct{}
func (a *damaged) open(d *door) {
fmt.Println("門已損壞,無法開啟")
}
func (a *damaged) close(d *door) {
fmt.Println("門已損壞,無法關(guān)閉")
}我們的門對象door實(shí)現(xiàn)了open和close方法,在方法中,只需要調(diào)用當(dāng)前狀態(tài)currentState的open和close方法即可。
測試代碼:
package state
func ExampleState() {
door := &door{}
// 開啟狀態(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:
// 門已開啟
// 關(guān)閉成功
// 開啟成功
// 門已關(guān)閉
// 門已損壞,無法開啟
// 門已損壞,無法關(guān)閉
}策略模式Strategy
問題
假設(shè)需要實(shí)現(xiàn)一組出行的功能,出現(xiàn)的方案可以選擇步行、騎行、開車,最簡單的做法就是分別實(shí)現(xiàn)這3種方法供客戶端調(diào)用。但這樣做就使對象與其代碼實(shí)現(xiàn)變得耦合了,客戶端需要決定出行方式,然后決定調(diào)用步行出行、騎行出行、開車出行等方法,這不符合開閉原則。
而策略模式的區(qū)別在于,它會將這些出行方案抽取到一組被稱為策略的類中,客戶端還是調(diào)用同一個(gè)出行對象,不需要關(guān)注實(shí)現(xiàn)細(xì)節(jié),只需要在參數(shù)中指定所需的策略即可。
解決
請看以下代碼:
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算法??蛻舳酥恍枰獔?zhí)行traffic方法即可,無需關(guān)注實(shí)現(xiàn)細(xì)節(jié)。
測試代碼:
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
問題
模板方法模式就是將算法分解為一系列步驟,然后在一個(gè)模版方法中依次調(diào)用這些步驟。這樣客戶端就不需要了解各個(gè)步驟的實(shí)現(xiàn)細(xì)節(jié),只需要調(diào)用模版即可。
解決
一個(gè)非常簡單的例子,請看以下代碼:
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ù)代碼……
}測試代碼:
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~
}訪問者模式Visitor
問題
訪問者模式試圖解決這樣一個(gè)問題:在不改變類的對象結(jié)構(gòu)的前提下增加新的操作。
解決
請看以下代碼:
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
}設(shè)計(jì)模式的“道”
上面那么多種設(shè)計(jì)模式你能記住幾種呢?設(shè)計(jì)模式分為“術(shù)”的部分和“道”的部分,上面那些設(shè)計(jì)模式就是“術(shù)”的部分,他們是一些圍繞著設(shè)計(jì)模式核心思路的經(jīng)典解決方案。換句話說,重要的是理解為什么要用那些設(shè)計(jì)模式,具體問題,具體分析,而不是把某種設(shè)計(jì)模式生搬硬套進(jìn)代碼。
設(shè)計(jì)模式有6大原則,以上的設(shè)計(jì)模式目的就是為了使軟件系統(tǒng)能達(dá)到這些原則:
開閉原則
軟件應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。
對系統(tǒng)進(jìn)行擴(kuò)展,而無需修改現(xiàn)有的代碼。這可以降低軟件的維護(hù)成本,同時(shí)也增加可擴(kuò)展性。
里氏替換原則
任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。
里氏替換原則是對開閉原則的補(bǔ)充,實(shí)現(xiàn)開閉原則的關(guān)鍵步驟就是抽象化,基類與子類的關(guān)系就是要盡可能的抽象化。
依賴倒置原則
面向接口編程,抽象不應(yīng)該依賴于具體類,具體類應(yīng)當(dāng)依賴于抽象。
這是為了減少類間的耦合,使系統(tǒng)更適宜于擴(kuò)展,也更便于維護(hù)。
單一職責(zé)原則
一個(gè)類應(yīng)該只有一個(gè)發(fā)生變化的原因。
一個(gè)類承載的越多,耦合度就越高。如果類的職責(zé)單一,就可以降低出錯(cuò)的風(fēng)險(xiǎn),也可以提高代碼的可讀性。
最少知道原則
一個(gè)實(shí)體應(yīng)當(dāng)盡量少地與其他實(shí)體之間發(fā)生相互作用。
還是為了降低耦合,一個(gè)類與其他類的關(guān)聯(lián)越少,越易于擴(kuò)展。
接口分離原則
使用多個(gè)專門的接口,而不使用高耦合的單一接口。
避免同一個(gè)接口占用過多的職責(zé),更明確的劃分,可以降低耦合。高耦合會導(dǎo)致程序不易擴(kuò)展,提高出錯(cuò)的風(fēng)險(xiǎn)。
到此這篇關(guān)于Go語言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用的文章就介紹到這了,更多相關(guān)Go語言 設(shè)計(jì)模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式的方法總結(jié)
這篇文章主要介紹了在?Go?語言中實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式的多種方法,并重點(diǎn)探討了通道、條件變量的適用場景和優(yōu)缺點(diǎn),需要的可參考一下2023-05-05
golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析
這篇文章主要為大家介紹了golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
Go?Singleflight導(dǎo)致死鎖問題解決分析
這篇文章主要為大家介紹了Go?Singleflight導(dǎo)致死鎖問題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Golang中時(shí)間格式化的實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了Go語言中進(jìn)行時(shí)間進(jìn)行格式化的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2023-09-09
gin自定義中間件解決requestBody不可重復(fù)讀問題(最新推薦)
這篇文章主要介紹了gin自定義中間件解決requestBody不可重復(fù)讀問題,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayo
這篇文章主要為大家介紹了Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayout方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

