Builder 模式


如果您有個物件必須建立,物件是由個別組件(Component)組合而成,個別組件建立非常複雜,但說明如何運用組件建立非 常簡單,您希望將建立複雜組件運用組件方式分離,則可以使用Builder模式。

舉 例來說,您想要建立一個迷宮產生程式,迷宮使用二維陣列來定義,0表示道路,1表示牆,2表示寶物,根據所定義的二維迷宮陣列,您想要程式自動產生各種不 同材質的迷宮,例如磚牆迷宮,鑽石迷宮等。在不同的繪圖引擎下,建立各種材質非常複雜,但建立迷宮的表達方式很簡單,像是這邊要道牆,那邊要條路,旁邊要 放個寶藏之類的。

您可以在程式中定義兩個角色,一個是指導迷宮建立流程的MazeDirector角色,一個是建立迷宮中每個區塊的MazeBuilder角色:
interface Maze {
public void paint();
}

interface MazeBuilder {
void buildRoad(int i, int j);
void buildWall(int i, int j);
void buildTreasure(int i, int j);
Maze getMaze();
}

class MazeDirector {
private int[][] maze;
private MazeBuilder builder;

MazeDirector(int[][] maze, MazeBuilder builder) {
this.maze = maze;
this.builder = builder;
}

Maze build() {
for(int i = 0; i < maze.length; i++) {
for(int j = 0; j < maze[i].length; j++) {
switch (maze[i][j]) {
case 0:
builder.buildRoad(i, j);
break;
case 1:
builder.buildWall(i, j);
break;
case 2:
builder.buildTreasure(i, j);
break;
}
}
}
return builder.getMaze();
}
}

您可以實作不同的MazeBuilder,使用不同的材質來建立迷宮中每個區塊,例如:
class PlainBuilder implements MazeBuilder {
private char[][] maze;

PlainBuilder(int i, int j) {
this.maze = new char[i][j];
}
public void buildRoad(int i, int j) {
maze[i][j] = ' ';
}
public void buildWall(int i, int j) {
maze[i][j] = '□';
}
public void buildTreasure(int i, int j) {
maze[i][j] = '★';
}

@Override
public Maze getMaze() {
return new Maze() {
@Override
public void paint() {
for(char[] row : maze) {
for(char ele : row) {
System.out.print(ele);
}
System.out.println();
}
}
};
}
}

public class Main {
public static void main(String[] args) {
final int[][] material = {
{1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 2, 1},
{1, 0, 1, 0, 1, 0, 1},
{1, 0, 2, 1, 0, 1, 1},
{1, 1, 0, 1, 0, 1, 1},
{1, 0, 0, 2, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1}};

MazeDirector director = new MazeDirector(material,
new PlainBuilder(material.length, material[0].length));
director.build().paint();
}
}

乍看之下,Builder模式與 Abstract Factory 模式 很類似,其中最主要的差別在於,Abstract Factory模式著重在不同的工廠實作提供不同的一組產品給組件使用,產品之間並不見得有「部份」(Part of)的概念。Builder模式則強調Builder中 所建立的組件,彼此之間有著「部份」(Part of)的概念,並依Director的流程來建立組件與組件之間的關係,也就是說,Builder 組件建立Director流程指導之間為彼此合作關 係(為強調出兩者關係,或許取名叫Director-Builder模式會更適合)。

可以使用下面的UML 類別圖來表示Builder模式:


圖中Builder介面指的是,物件必須具有Builder所定義之公開協 定,而非專指Java中的interface定義。對於靜態語言來說,例如Java,必須使用型態來宣告變數,因此根據需 求,可以使用interfact或abstract class來定義Builder所定義之公開協定。對於動態語言來 說,例如Python,真正的型態資訊是在物件之上(而非變數),因此要求的是物件必須具有Buildert之公開方法 (無論是「哪一種」物件)。若以Python實作,以下是個參考範例:
class Maze:
def __init__(self, maze):
self.maze = maze
def paint(self):
for row in self.maze:
for c in row:
print(c, end="")
print()

class MazeBuilder:
def __init__(self, row, column):
self.maze = [[''] * column for i in range(row)]
def buildRoad(self, i, j):
self.maze[i][j] = ' '
def buildWall(self, i, j):
self.maze[i][j] = '□'
def buildTreasure(self, i, j):
self.maze[i][j] = '★'
def getMaze(self):
return Maze(self.maze)

class MazeDirector:
def __init__(self, maze, builder):
self.maze = maze
self.builder = builder
def build(self):
for i in range(len(self.maze)):
for j in range(len(self.maze[i])):
if self.maze[i][j] == 0:
self.builder.buildRoad(i, j)
elif self.maze[i][j] == 1:
self.builder.buildWall(i, j)
elif self.maze[i][j] == 2:
self.builder.buildTreasure(i, j)
return builder.getMaze()

material = [[1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 2, 1],
[1, 0, 1, 0, 1, 0, 1],
[1, 0, 2, 1, 0, 1, 1],
[1, 1, 0, 1, 0, 1, 1],
[1, 0, 0, 2, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1]]

builder = MazeBuilder(len(material), len(material[0]))
director = MazeDirector(material, builder)
director.build().paint()

Gof 中有給出了一個不錯的例子,以設計文件剖析器為例,該剖析器可以將文件轉換為其它的格式,以 DOC文件剖析器為例好了,假設希望析剖器可以將DOC文件轉換為RTF或是PDF文件,可以如下設計結構: