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

三、測試驅動開發
根據前文的設計過程,我們將建造一個 director 變量:ManufacturingDirector , 以使用由汽車和摩托車產品建造器的建造過程。dierctor 是負責人,builder 是實際的建造者。
1、Builder 接口聲明
Builder 聲明如下:
package creational
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
- BuildProcess 接口定義了建造車輛所需的步驟,因此,各個車輛的Builder 必須實現這個接口。
- 在每個SetXXX() 的函數,返回每一個構建的過程,然后將各個步驟連接起來,返回一個GetVehicle() 的方法。
2、Director 主管接口
ManufacturingDirector 主管接口可以來自接收不同的 Builder:
- 然后有一個Construct() 方法使用Builder 來重復建造過程,后面會實現這個方法。
- SetBuilder() 方法用于更換不同的Builder。
// 制作主管
type ManufacturingDirector struct{}
func (f *ManufacturingDirector) Construct() {// 建筑過程
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {// 選擇建造者
}
3、Product 產品結構體
產品是我們在制造出的最終對象。在上面的簡易例子中,我們假設一輛交通工具是由車輪、座椅和結構組成的。
// 產品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
4、Builder 具體建造者
第一個 Builder 為 Car 建造者 Builder,需要我們實現定義在 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
// 建造過程
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
// 制作主管
type ManufacturingDirector struct{}
func (f *ManufacturingDirector) Construct() {
// 等待實現
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
// 等待實現
}
// 產品
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、編寫測試用例
針對上面編寫的建造過程,我們可以進行如下的測試,同目錄下創建 creational_test.go 文件。
1、首先是測試汽車建造過程,假定最終生產的汽車是具有 4 個輪子,5 個座位,然后結構是 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 個簡單的測試檢查是否建造出汽車類型。運行單元測試,結果如下:
$ 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 個測試單元都顯示失敗,接著我們來看一下摩托車的單元測試怎么寫的。
2、摩托車 motorCycle 的單元測試如下:
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)
}
建造過程跟 car 類似,我們只需要向 manufacturingComplex.SetBuilder(motorBuilder) 傳遞 motorBuilder 即可,我們假定摩托車有 2 個輪子,2 個座位,結構必須為 MotorCyle。
運行測試代碼,得到如下結果:
$ 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
可以看到,單元測試也是失敗的,因為我們還沒有完成實現具體的建造者模式。接下來就是具體實現的過程。
四、建造者模式 Go 實現
為了實現建造者,想必你也開始有了一點點自己的思路吧。再來實現我們之前創建的 creation.go 文件,全新的 creational.go 文件代碼如下:
package creational
// 建造過程
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
}
// 產品
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)}
}
實現完所有的方法之后,再看運行 go test -v . 執行后的測試結果:
$ go test -v .
=== RUN TestBuilderPattern
--- PASS: TestBuilderPattern (0.00s)
PASS
ok github.com/yuzhoustayhungry/GoDesignPattern/creational 0.255s
恭喜,至此,測試用例全部通過。你也可以看到,建造者模式是一個可重復的模式,但在 BuildProcess 接口的每個方法內,我們可以封裝盡可能多的復雜對象,這樣,用戶其實并不知道關于對象創建的細節。
五、建造者模式總結
就像制作汽車一樣,建造者模式的核心在于如何一步一步地構建一個包含多個組成部件的完整對象,使用相同的構建過程構建不同的產品。
在軟件開發過程中,如果需要創建復雜對象,并希望系統具備很好的靈活性和可擴展性,可以考慮使用建造者模式。
??想了解更多關于開源的內容,請訪問:??
??51CTO 開源基礎軟件社區??
??https://ost.51cto.com??。