欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語(yǔ)言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用

 更新時(shí)間:2022年05月10日 09:19:49   作者:xinxin  
設(shè)計(jì)模式是軟件工程中各種常見(jiàn)問(wèn)題的經(jīng)典解決方案,,本文主要介紹了Go語(yǔ)言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

設(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ǔ)法筆記示例

    這篇文章主要為大家介紹了go語(yǔ)言beego框架web開(kāi)發(fā)語(yǔ)法筆記示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 使用Go開(kāi)發(fā)硬件驅(qū)動(dòng)程序的流程步驟

    使用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-11
  • Go語(yǔ)言實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式的方法總結(jié)

    Go語(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-05
  • golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析

    golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析

    這篇文章主要為大家介紹了golang?pprof?監(jiān)控系列?go?trace統(tǒng)計(jì)原理與使用解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Golang匯編之控制流深入分析講解

    Golang匯編之控制流深入分析講解

    這篇文章主要介紹了Golang匯編之控制流,程序執(zhí)行的流程主要有順序、分支和循環(huán)幾種執(zhí)行流程,本節(jié)主要討論如何將Go語(yǔ)言的控制流比較直觀地轉(zhuǎn)譯為匯編程序,或者說(shuō)如何以匯編思維來(lái)編寫(xiě)Go語(yǔ)言代碼,感興趣的同學(xué)可以參考下文
    2023-05-05
  • 一文了解Go語(yǔ)言的并發(fā)特性

    一文了解Go語(yǔ)言的并發(fā)特性

    本文主要介紹了一文了解Go語(yǔ)言的并發(fā)特性,通過(guò)輕量級(jí)線程、通道及選擇語(yǔ)句,使得并發(fā)編程變得既簡(jiǎn)單又高效,下面就來(lái)具體了解一下如何使用,感興趣的可以了解一下
    2024-02-02
  • Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析

    Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析

    這篇文章主要為大家介紹了Go?Singleflight導(dǎo)致死鎖問(wèn)題解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang中時(shí)間格式化的實(shí)現(xiàn)詳解

    Golang中時(shí)間格式化的實(shí)現(xiàn)詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中進(jìn)行時(shí)間進(jìn)行格式化的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-09-09
  • gin自定義中間件解決requestBody不可重復(fù)讀問(wèn)題(最新推薦)

    gin自定義中間件解決requestBody不可重復(fù)讀問(wèn)題(最新推薦)

    這篇文章主要介紹了gin自定義中間件解決requestBody不可重復(fù)讀問(wèn)題,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayout

    Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayo

    這篇文章主要為大家介紹了Go Excelize API源碼解讀GetSheetViewOptions與SetPageLayout方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08

最新評(píng)論