
??想了解更多關(guān)于開(kāi)源的內(nèi)容,請(qǐng)?jiān)L問(wèn):??
??51CTO 開(kāi)源基礎(chǔ)軟件社區(qū)??
??https://ost.51cto.com??
一、生活案例
沙師弟 : “大師兄,車是怎么建成的???”
大師兄:“從外部看,車由車身、座椅和輪胎,從內(nèi)部又有引擎、方向盤、電路系統(tǒng)、剎車系統(tǒng)、冷卻系統(tǒng)等等組成,這些復(fù)雜的部件一般都不是一個(gè)廠商來(lái)完成的,而是將這些交付給汽車零部件制造商。不同的生產(chǎn)商來(lái)最終完成不同部件的生產(chǎn),采購(gòu)?fù)暾麄€(gè)零部件,最后在車間完成整個(gè)組裝。”
汽車這個(gè)復(fù)雜的對(duì)象就可以通過(guò)建造者模式來(lái)將部件和組裝過(guò)程分開(kāi),幫我們快速完成汽車的建造。
二、建造者模式
構(gòu)建者模式幫助我們構(gòu)建復(fù)雜的對(duì)象,而不需要直接實(shí)例化它們的結(jié)構(gòu),或編寫它們所需的邏輯。想象一下,一個(gè)對(duì)象可能有幾十個(gè)字段,它們本身就是比較復(fù)雜的結(jié)構(gòu)。
現(xiàn)在,你有許多具有這些特征的對(duì)象,還可以有更多。我們不希望在包中寫創(chuàng)建所有這些對(duì)象的邏輯,而只在需要使用這些對(duì)象的地方寫好。
1、Go 中的對(duì)象實(shí)例
在 Go 語(yǔ)言中,實(shí)例的創(chuàng)建可以很簡(jiǎn)單,比如只是簡(jiǎn)單提供 ??{}?
? ,然后讓實(shí)例的值為零;也可以很復(fù)雜,比如一個(gè)對(duì)象需要進(jìn)行一些 API 調(diào)用,檢查狀態(tài),并為其字段創(chuàng)建對(duì)象。
你也可以有一個(gè)由多個(gè)對(duì)象組成的對(duì)象,這在 Go 中是非常常見(jiàn)的,因?yàn)?Go 不支持繼承。
同時(shí),你可以用同樣的技術(shù)來(lái)創(chuàng)造許多類型的對(duì)象。例如,你將使用幾乎相同的技術(shù)來(lái)建造一輛汽車和一輛公共汽車,只是它們的尺寸和座位數(shù)不同,所以我們?yōu)槭裁床恢貜?fù)使用建造過(guò)程呢?這就是建造者模式的用武之地了。
2、建造者模式的優(yōu)點(diǎn)
- 對(duì)復(fù)雜的創(chuàng)建進(jìn)行抽象,以便將對(duì)象與對(duì)象的使用者進(jìn)行分開(kāi)
- 通過(guò)填入字段和創(chuàng)建嵌入對(duì)象,一步步創(chuàng)建對(duì)象
- 能夠在許多對(duì)象之間重復(fù)使用對(duì)象創(chuàng)建方法
3、交通工具制造的例子
建造者模式通常被描述為一個(gè)主管 director、幾個(gè) Builder 和他們所創(chuàng)建的產(chǎn)品之間的關(guān)系。
我們來(lái)看關(guān)于汽車的例子,創(chuàng)建一個(gè)車輛建造器 Builder,建造車(Product)的過(guò)程或許會(huì)有一些差異,但對(duì)每一種車輛來(lái)說(shuō),整體的過(guò)程可以歸納為如下步驟:
- 選擇車輛類型
- 組裝結(jié)構(gòu)
- 安裝車輪
- 放置座椅
如果你仔細(xì)思考,你可以重復(fù)通過(guò)這個(gè)步驟描述建造一輛騎車和一輛摩托車,在接下來(lái)的例子中,主管 director 的角色就是用 Manufacturing 變量進(jìn)行表示。
4、設(shè)計(jì)思路
正如上述的描述那樣,我們必須處理一些 Builder 變量和一個(gè)獨(dú)立的 director . 主管 director 來(lái)領(lǐng)導(dǎo)實(shí)際建造者 Builder 的建造產(chǎn)品的過(guò)程。因此,對(duì)于一個(gè)車輛建造者的要求是:
- 需要有一個(gè)制造對(duì)象來(lái)制造交通工具的一切
- 當(dāng)使用汽車建造者Builder 時(shí),返回的車輛產(chǎn)品必須帶有 4 個(gè)輪子、5 個(gè)座椅和定義為Car 的結(jié)構(gòu)體
- 使用摩托車建造者時(shí),返回的車輛產(chǎn)品必須帶有 2 個(gè)輪子、供 2 個(gè)人的座位和定義為Motorbike 的結(jié)構(gòu)體
- 任何BuilderProcess 建造者必須開(kāi)放對(duì)車輛產(chǎn)品的修改功能
結(jié)構(gòu)圖如下:

三、測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
根據(jù)前文的設(shè)計(jì)過(guò)程,我們將建造一個(gè) director 變量:ManufacturingDirector , 以使用由汽車和摩托車產(chǎn)品建造器的建造過(guò)程。dierctor 是負(fù)責(zé)人,builder 是實(shí)際的建造者。
1、Builder 接口聲明
Builder 聲明如下:
package creational
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
- BuildProcess 接口定義了建造車輛所需的步驟,因此,各個(gè)車輛的Builder 必須實(shí)現(xiàn)這個(gè)接口。
- 在每個(gè)SetXXX() 的函數(shù),返回每一個(gè)構(gòu)建的過(guò)程,然后將各個(gè)步驟連接起來(lái),返回一個(gè)GetVehicle() 的方法。
2、Director 主管接口
ManufacturingDirector 主管接口可以來(lái)自接收不同的 Builder:
- 然后有一個(gè)Construct() 方法使用Builder 來(lái)重復(fù)建造過(guò)程,后面會(huì)實(shí)現(xiàn)這個(gè)方法。
- SetBuilder() 方法用于更換不同的Builder。
// 制作主管
type ManufacturingDirector struct{}
func (f *ManufacturingDirector) Construct() {// 建筑過(guò)程
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {// 選擇建造者
}
3、Product 產(chǎn)品結(jié)構(gòu)體
產(chǎn)品是我們?cè)谥圃斐龅淖罱K對(duì)象。在上面的簡(jiǎn)易例子中,我們假設(shè)一輛交通工具是由車輪、座椅和結(jié)構(gòu)組成的。
// 產(chǎn)品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
4、Builder 具體建造者
第一個(gè) Builder 為 Car 建造者 Builder,需要我們實(shí)現(xiàn)定義在 BuildProcess 接口的方法:
// 汽車建造者
type CarBuilder struct{}
func (c *CarBuilder) SetWheels() BuildProcess {return nil
}
func (c *CarBuilder) SetSeats() BuildProcess {return nil
}
func (c *CarBuilder) SetStructure() BuildProcess {return nil
}
func (c *CarBuilder) GetVehicle() VehicleProduct {return VehicleProduct{}
}
同理,摩托車建造者如下:
// 摩托車建造者
type MotorBuilder struct{}
func (m *MotorBuilder) SetWheels() BuildProcess {return nil
}
func (m *MotorBuilder) SetSeats() BuildProcess {return nil
}
func (m *MotorBuilder) SetStructure() BuildProcess {return nil
}
func (m *MotorBuilder) GetVehicle() VehicleProduct {return VehicleProduct{}
}
最終,我們得到完整的 creational.go 文件:
package creational
// 建造過(guò)程
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
// 制作主管
type ManufacturingDirector struct{}
func (f *ManufacturingDirector) Construct() {
// 等待實(shí)現(xiàn)
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
// 等待實(shí)現(xiàn)
}
// 產(chǎn)品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
// 汽車建造者
type CarBuilder struct{}
func (c *CarBuilder) SetWheels() BuildProcess {
return nil
}
func (c *CarBuilder) SetSeats() BuildProcess {
return nil
}
func (c *CarBuilder) SetStructure() BuildProcess {
return nil
}
func (c *CarBuilder) GetVehicle() VehicleProduct {
return VehicleProduct{}
}
// 摩托車建造者
type MotorBuilder struct{}
func (m *MotorBuilder) SetWheels() BuildProcess {
return nil
}
func (m *MotorBuilder) SetSeats() BuildProcess {
return nil
}
func (m *MotorBuilder) SetStructure() BuildProcess {
return nil
}
func (m *MotorBuilder) GetVehicle() VehicleProduct {
return VehicleProduct{}
}
5、編寫測(cè)試用例
針對(duì)上面編寫的建造過(guò)程,我們可以進(jìn)行如下的測(cè)試,同目錄下創(chuàng)建 creational_test.go 文件。
1、首先是測(cè)試汽車建造過(guò)程,假定最終生產(chǎn)的汽車是具有 4 個(gè)輪子,5 個(gè)座位,然后結(jié)構(gòu)是 Car 類型,寫入如下代碼:
package creational
import "testing"
func TestBuilderPattern(t *testing.T) {
manufacturingComplex := ManufacturingDirector{}
carBuilder := &CarBuilder{}
manufacturingComplex.SetBuilder(carBuilder)
manufacturingComplex.Construct()
car := carBuilder.GetVehicle()
if car.Wheels != 4 {
t.Errorf("Wheels on a car must be 4 and they were %d\n", car.Wheels)
}
if car.Structure != "Car" {
t.Errorf("Structure on a car must be 'Car' and was %s\n", car.Structure)
}
if car.Seats != 5 {
t.Errorf("Seats on a car must be 5 and they were %d\n", car.Seats)
}
}
我們寫了 3 個(gè)簡(jiǎn)單的測(cè)試檢查是否建造出汽車類型。運(yùn)行單元測(cè)試,結(jié)果如下:
$ go test -v .
=== RUN TestBuilderPattern
creational_test.go:16: Wheels on a car must be 4 and they were 0
creational_test.go:20: Structure on a car must be 'Car' and was
creational_test.go:24: Seats on a car must be 5 and they were 0
--- FAIL: TestBuilderPattern (0.00s)
FAIL
FAIL github.com/yuzhoustayhungry/GoDesignPattern/creational 0.860s
FAIL
如上顯示,3 個(gè)測(cè)試單元都顯示失敗,接著我們來(lái)看一下摩托車的單元測(cè)試怎么寫的。
2、摩托車 motorCycle 的單元測(cè)試如下:
motorBuilder := &MotorBuilder{}
manufacturingComplex.SetBuilder(motorBuilder)
manufacturingComplex.Construct()
motorCycle := motorBuilder.GetVehicle()
if motorCycle.Wheels != 2 {
t.Errorf("Wheels on a motorCycle must be 2 and they were %d\n",
motorCycle.Wheels)
}
if motorCycle.Structure != "MotorCycle" {
t.Errorf("Structure on a motorCycle must be 'MotorCycle' and was %s\n",
motorCycle.Structure)
}
if motorCycle.Seats != 2 {
t.Errorf("Seats on a motorCycle must be 2 and was %d\n", motorCycle.Seats)
}
建造過(guò)程跟 car 類似,我們只需要向 manufacturingComplex.SetBuilder(motorBuilder) 傳遞 motorBuilder 即可,我們假定摩托車有 2 個(gè)輪子,2 個(gè)座位,結(jié)構(gòu)必須為 MotorCyle。
運(yùn)行測(cè)試代碼,得到如下結(jié)果:
$ go test -v .
=== RUN TestBuilderPattern
creational_test.go:16: Wheels on a car must be 4 and they were 0
creational_test.go:20: Structure on a car must be 'Car' and was
creational_test.go:24: Seats on a car must be 5 and they were 0
creational_test.go:36: Wheels on a motorCycle must be 2 and they were 0
creational_test.go:41: Structure on a motorCycle must be 'MotorCycle' and was
creational_test.go:46: Seats on a motorCycle must be 2 and was 0
--- FAIL: TestBuilderPattern (0.00s)
FAIL
FAIL github.com/yuzhoustayhungry/GoDesignPattern/creational 0.595s
FAIL
可以看到,單元測(cè)試也是失敗的,因?yàn)槲覀冞€沒(méi)有完成實(shí)現(xiàn)具體的建造者模式。接下來(lái)就是具體實(shí)現(xiàn)的過(guò)程。
四、建造者模式 Go 實(shí)現(xiàn)
為了實(shí)現(xiàn)建造者,想必你也開(kāi)始有了一點(diǎn)點(diǎn)自己的思路吧。再來(lái)實(shí)現(xiàn)我們之前創(chuàng)建的 creation.go 文件,全新的 creational.go 文件代碼如下:
package creational
// 建造過(guò)程
type BuildProcess interface {SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
// 制作主管
type ManufacturingDirector struct {
builder BuildProcess
}
func (f *ManufacturingDirector) Construct() {//
f.builder.SetSeats().SetStructure().SetWheels()
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {//
f.builder = b
}
// 產(chǎn)品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
// 汽車建造者
type CarBuilder struct {
v VehicleProduct
}
func (c *CarBuilder) SetWheels() BuildProcess {// return nil
c.v.Wheels = 4return c
}
func (c *CarBuilder) SetSeats() BuildProcess {// return nil
c.v.Seats = 5return c
}
func (c *CarBuilder) SetStructure() BuildProcess {// return nil
c.v.Structure = "Car"return c
}
func (c *CarBuilder) GetVehicle() VehicleProduct {// return VehicleProduct{}return c.v
}
// 摩托車建造者
type MotorBuilder struct {
v VehicleProduct
}
func (m *MotorBuilder) SetWheels() BuildProcess {// return nil
m.v.Wheels = 2return m
}
func (m *MotorBuilder) SetSeats() BuildProcess {// return nil
m.v.Seats = 2return m
}
func (m *MotorBuilder) SetStructure() BuildProcess {// return nil
m.v.Structure = "MotorCycle"return m
}
func (m *MotorBuilder) GetVehicle() VehicleProduct {// return VehicleProduct{}return m.v
}
更改后的 creational_test.go 文件如下:
package creational
import "testing"
func TestBuilderPattern(t *testing.T) {
manufacturingComplex := ManufacturingDirector{}
carBuilder := &CarBuilder{}
manufacturingComplex.SetBuilder(carBuilder)
manufacturingComplex.Construct()
car := carBuilder.GetVehicle()
if car.Wheels != 4 {
t.Errorf("Wheels on a car must be 4 and they were %d\n", car.Wheels)}
if car.Structure != "Car" {
t.Errorf("Structure on a car must be 'Car' and was %s\n", car.Structure)}
if car.Seats != 5 {
t.Errorf("Seats on a car must be 5 and they were %d\n", car.Seats)}
motorBuilder := &MotorBuilder{}
manufacturingComplex.SetBuilder(motorBuilder)
manufacturingComplex.Construct()
motorCycle := motorBuilder.GetVehicle()
if motorCycle.Wheels != 2 {
t.Errorf("Wheels on a motorCycle must be 2 and they were %d\n",
motorCycle.Wheels)}
if motorCycle.Structure != "MotorCycle" {
t.Errorf("Structure on a motorCycle must be 'MotorCycle' and was %s\n",
motorCycle.Structure)}
if motorCycle.Seats != 2 {
t.Errorf("Seats on a motorCycle must be 2 and was %d\n", motorCycle.Seats)}
}
實(shí)現(xiàn)完所有的方法之后,再看運(yùn)行 go test -v . 執(zhí)行后的測(cè)試結(jié)果:
$ go test -v .
=== RUN TestBuilderPattern
--- PASS: TestBuilderPattern (0.00s)
PASS
ok github.com/yuzhoustayhungry/GoDesignPattern/creational 0.255s
恭喜,至此,測(cè)試用例全部通過(guò)。你也可以看到,建造者模式是一個(gè)可重復(fù)的模式,但在 BuildProcess 接口的每個(gè)方法內(nèi),我們可以封裝盡可能多的復(fù)雜對(duì)象,這樣,用戶其實(shí)并不知道關(guān)于對(duì)象創(chuàng)建的細(xì)節(jié)。
五、建造者模式總結(jié)
就像制作汽車一樣,建造者模式的核心在于如何一步一步地構(gòu)建一個(gè)包含多個(gè)組成部件的完整對(duì)象,使用相同的構(gòu)建過(guò)程構(gòu)建不同的產(chǎn)品。
在軟件開(kāi)發(fā)過(guò)程中,如果需要?jiǎng)?chuàng)建復(fù)雜對(duì)象,并希望系統(tǒng)具備很好的靈活性和可擴(kuò)展性,可以考慮使用建造者模式。
??想了解更多關(guān)于開(kāi)源的內(nèi)容,請(qǐng)?jiān)L問(wèn):??
??51CTO 開(kāi)源基礎(chǔ)軟件社區(qū)??
??https://ost.51cto.com??。