
生成器 和 抽象工廠 一樣同屬于創(chuàng)建型模式。
介紹
意圖
將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
適用性
- 當(dāng)創(chuàng)建復(fù)雜對(duì)象的算法應(yīng)該獨(dú)立于該對(duì)象的組成部分已經(jīng)它們的裝配方式時(shí)。
- 當(dāng)構(gòu)造過程必須允許被構(gòu)造的對(duì)象有不同的表示時(shí)。
結(jié)構(gòu)

參與者
- Builder
為創(chuàng)建一個(gè) Product 對(duì)象的各個(gè)部件指定抽象接口。
- ConcreteBuilder
實(shí)現(xiàn) Builder 的接口已構(gòu)造和裝配該產(chǎn)品的各個(gè)部件。
定義并明確它所創(chuàng)建的表示。
提供一個(gè)檢索產(chǎn)品的接口。
- Director
構(gòu)造一個(gè) Builder 接口對(duì)象。
- Product
協(xié)助

- 客戶創(chuàng)建 Director 對(duì)象,比通過他想要的 Builder 對(duì)象進(jìn)行配置。
- 一旦產(chǎn)品部件被生成,向?qū)骶蜁?huì)通知生成器。
- 客戶從生成器中檢索產(chǎn)品。
效果
- 它使你可以改變一個(gè)產(chǎn)品的內(nèi)部表示
Builder 對(duì)象提供給導(dǎo)向器一個(gè)構(gòu)造產(chǎn)品的抽象接口。該接口使得生成器可以隱藏產(chǎn)品的表示和內(nèi)部結(jié)構(gòu)。
- 它將構(gòu)造代碼和表示代碼分開
- 他可以對(duì)構(gòu)造過程進(jìn)行更精細(xì)的控制
Builder 模式與一下子就生成產(chǎn)品的創(chuàng)建型模式不同,它是在導(dǎo)向器控制下一步一步構(gòu)造產(chǎn)品的。
實(shí)現(xiàn)
- 裝配和構(gòu)造接口
- 為什么產(chǎn)品沒有抽象類
由于具體生成器生成的產(chǎn)品,它們的表示相差很大,以至于給不同的產(chǎn)品公共的父類并不現(xiàn)實(shí)。
- 在 Builder 中缺省的方法為空
示例
定義一個(gè)生成器 MazeBuilder
protocol MazeBuilder {
func buildMaze()
func buildRoom(n: Int)
func buildDoor(roomFrom: Int, roomTo: Int)
func getMaze() -> Maze?
}
extension MazeBuilder {
func buildMaze() {}
func buildRoom(n: Int) {}
func buildDoor(roomFrom: Int, roomTo: Int) { }
func getMaze() -> Maze? { return nil }
}
主要接口有:
- 創(chuàng)建迷宮接口
- 創(chuàng)建房間接口
- 創(chuàng)建門的接口
MazeBuilder 并不用來構(gòu)建迷宮,它主要用來定義接口。
現(xiàn)在我們定義一個(gè)用來構(gòu)建迷宮的類 StandarMazeBuilder 。
這里 StandarMazeBuilder 我選擇使用 class, 因?yàn)樯善髟趨f(xié)助中有一點(diǎn) 客戶從生成器中檢索產(chǎn)品。也就是說用戶使用生成器的方法構(gòu)造產(chǎn)品后,還可以使用通過現(xiàn)在生成器對(duì)象進(jìn)行檢索產(chǎn)品。這樣使用引用類型是更好的選擇。
class StandarMazeBuilder: MazeBuilder {
var currentMaze: Maze?
func buildMaze() {
currentMaze = Maze()
}
func buildRoom(n: Int) {
var room = Room(no: n)
room.setSide(dect: .east, site: Wall())
room.setSide(dect: .north, site: Wall())
room.setSide(dect: .south, site: Wall())
room.setSide(dect: .west, site: Wall())
currentMaze?.addRoom(room: room)
}
func commonWall(r1: Room, r2: Room) -> Direction {
return .north
}
func buildDoor(roomFrom: Int, roomTo: Int) {
var r1 = currentMaze!.getRoom(roomFrom)
var r2 = currentMaze!.getRoom(roomTo)
let d = Door(r1: r1, r2: r2)
r1.setSide(dect: commonWall(r1: r1, r2: r2), site: d)
r2.setSide(dect: commonWall(r1: r2, r2: r1), site: d)
}
func getMaze() -> Maze? {
return currentMaze
}
}
這里 commonWall 是個(gè)功能性操作,決定兩個(gè)房間的公共墻壁的方位(為了簡單處理我這里就固定返回一個(gè)值吧)
最后我們需要定義一個(gè)導(dǎo)向器。
struct MazeGame {
func createMaze(builder: MazeBuilder) -> Maze? {
builder.buildMaze()
builder.buildRoom(n: 1)
builder.buildRoom(n: 2)
builder.buildDoor(roomFrom: 1, roomTo: 2)
return builder.getMaze()
}
}
現(xiàn)在再看看如何使用這個(gè)生成器來構(gòu)造迷宮。
let game = MazeGame()
let builder = StandarMazeBuilder()
game.createMaze(builder: builder)
let maze = builder.getMaze()
print("\(maze!)")
打印信息:
===========================
Maze room:
room_2 Room
north is Optional(Wall)
south is Optional(Wall)
east is Optional(Wall)
west is Optional(Wall)
room_1 Room
north is Optional(Wall)
south is Optional(Wall)
east is Optional(Wall)
west is Optional(Wall)
===========================
到現(xiàn)在生成器模式看上去好像沒什么特別之處。
那接下來我們就來完成另外一個(gè)需求,我們不去構(gòu)建迷宮,只對(duì)迷宮的構(gòu)件進(jìn)行計(jì)數(shù)。
這個(gè)看起來和原來用例構(gòu)建迷宮的代碼有則很大的差異。
但我們使用生成器模式就不用懼怕這樣的差異。我們重新定義一個(gè) CountingMazeBuilder 生成器就可以了。
class CountingMazeBuilder: MazeBuilder {
var doors = 0
var rooms = 0;
func buildDoor(roomFrom: Int, roomTo: Int) {
doors += 1
}
func buildRoom(n: Int) {
rooms += 1
}
func getCounts() -> (r: Int, d: Int) {
return (rooms, doors)
}
}
我們再看看用戶如何使用這個(gè)生成器,代碼視乎沒有太大的變化。
let game = MazeGame()
let countingBuilder = CountingMazeBuilder()
game.createMaze(builder: countingBuilder)
let count = countingBuilder.getCounts()
print("The maze has \nrooms \(count.r) \ndoors \(count.d)")
打印信息:
The maze has
rooms 2
doors 1
最后來點(diǎn)總結(jié)
相比抽象工廠模式,生成器模式更擅長于對(duì)內(nèi)部表示存在較大差異的產(chǎn)品定義統(tǒng)一的接口。使得構(gòu)建代碼和表示代碼分離。
最最后 歡迎討論、批評(píng)、指錯(cuò)。