生成器
和 抽象工廠
一樣同屬于創建型模式。
介紹
意圖
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
適用性
- 當創建復雜對象的算法應該獨立于該對象的組成部分已經它們的裝配方式時。
- 當構造過程必須允許被構造的對象有不同的表示時。
結構
參與者
- Builder
為創建一個 Product 對象的各個部件指定抽象接口。
- ConcreteBuilder
實現 Builder 的接口已構造和裝配該產品的各個部件。
定義并明確它所創建的表示。
提供一個檢索產品的接口。
- Director
構造一個 Builder 接口對象。
- Product
協助
- 客戶創建 Director 對象,比通過他想要的 Builder 對象進行配置。
- 一旦產品部件被生成,向導器就會通知生成器。
- 客戶從生成器中檢索產品。
效果
- 它使你可以改變一個產品的內部表示
Builder 對象提供給導向器一個構造產品的抽象接口。該接口使得生成器可以隱藏產品的表示和內部結構。
- 它將構造代碼和表示代碼分開
- 他可以對構造過程進行更精細的控制
Builder 模式與一下子就生成產品的創建型模式不同,它是在導向器控制下一步一步構造產品的。
實現
- 裝配和構造接口
- 為什么產品沒有抽象類
由于具體生成器生成的產品,它們的表示相差很大,以至于給不同的產品公共的父類并不現實。
- 在 Builder 中缺省的方法為空
示例
定義一個生成器 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 }
}
主要接口有:
- 創建迷宮接口
- 創建房間接口
- 創建門的接口
MazeBuilder
并不用來構建迷宮,它主要用來定義接口。
現在我們定義一個用來構建迷宮的類 StandarMazeBuilder
。
這里 StandarMazeBuilder
我選擇使用 class
, 因為生成器在協助中有一點 客戶從生成器中檢索產品
。也就是說用戶使用生成器的方法構造產品后,還可以使用通過現在生成器對象進行檢索產品。這樣使用引用類型是更好的選擇。
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
是個功能性操作,決定兩個房間的公共墻壁的方位(為了簡單處理我這里就固定返回一個值吧)
最后我們需要定義一個導向器。
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()
}
}
現在再看看如何使用這個生成器來構造迷宮。
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)
===========================
到現在生成器模式看上去好像沒什么特別之處。
那接下來我們就來完成另外一個需求,我們不去構建迷宮,只對迷宮的構件進行計數。
這個看起來和原來用例構建迷宮的代碼有則很大的差異。
但我們使用生成器模式就不用懼怕這樣的差異。我們重新定義一個 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)
}
}
我們再看看用戶如何使用這個生成器,代碼視乎沒有太大的變化。
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
最后來點總結
相比抽象工廠模式,生成器模式更擅長于對內部表示存在較大差異的產品定義統一的接口。使得構建代碼和表示代碼分離。
最最后 歡迎討論、批評、指錯。