前面我們總結了Builder模式的構成、簡單的使用例子,現在我們來看Builder模式的一個完整例子,該例子模擬了一個后臺定時運行Job的構建過程:
一、需求
用戶可以創建一個定時運行的Job,該Job具有:任務Id,創建者,開始時間,結束時間,任務狀態。
二、基本結構
下面我們來看一張圖,從這張圖我們可以了解整個程序及Builder模式的構成

我們可以看到在這個包圖中,有4個比較重要的類、接口:
1. Builder:包含了一個抽象的方法build
2. JobBuilder:實現了Builder接口,在其build方法中實現了Job對象的構建。注意其中包含了Job對象所有屬性的一份拷貝,這是為了保存臨時緩存數據用的
3. Job:定時任務類,包含了一個package access權限的構造方法和一系列get方法,要注意的是沒有set方法
4.JobParser:Job解析器,用于解析用戶從GUI界面輸入的各種文本參數
好,下面我們再來看一張圖,看看這些類之間到底是怎么合作的:

可以看到JobParser類會在解析構建參數的過程中不斷調用JobBuilder類的set方法,將解析后的臨時數據保存到Builder的緩存變量中,而當解析完成后Builder就會從自身中取出所有的臨時數據,用來構建最終的目標對象。這個過程中最終構建的參數可能由于校驗而發生了變化(例如:對缺少變量的默認賦值)。
三、代碼示例
1. Job
1
/** *//**
2
* Instantiates a new job.
3
*
4
* @param id the id
5
* @param description the description
6
* @param owner the owner
7
* @param start the start
8
* @param end the end
9
* @param status the status
10
*/
11
Job(long id, String description, String owner, Date start, Date end,
12
String status)
{
13
this.jobId = id;
14
this.jobDescription = description;
15
this.jobOwner = owner;
16
this.jobStartTime = start;
17
this.jobEndTime = end;
18
this.jobStatus = status;
19
}
20
2. Builder
1
package org.pattern.build;
2
3
/** *//**
4
* <pre>
5
* Builder接口是代表了一個生成器,它通過其build方法生成一個對象
6
* 然后返回給調用者
7
* </pre>
8
*
9
* @author Paul Lin
10
* @version 1.0
11
* @uml.dependency supplier="org.pattern.build.BuilderException"
12
*/
13
public interface Builder
{
14
15
/** *//**
16
* Builds the destination object. The implement class
17
* should be return an Object that represent the fianl
18
* object
19
*
20
* @return the object
21
*
22
* @throws BuilderException the builder exception
23
*/
24
public Object build() throws BuilderException;
25
}
3. JobBuilder
1
public Job build() throws BuilderException
{
2
boolean valid = true;
3
String errorReason = "";
4
if (jobId <= 0)
{
5
valid = false;
6
errorReason = " Id should be large than 0.";
7
}
8
if (jobOwner == null || (jobOwner.trim().length() == 0))
{
9
valid = false;
10
errorReason = " Job owner shoud not be null.";
11
}
12
if (jobStartTime == null)
{
13
valid = false;
14
errorReason = " Job start time shoud not be null.";
15
}
16
if ((jobStartTime.getTime() - jobEndTime.getTime()) > 0)
{
17
valid = false;
18
errorReason = " Job start time should be less or equals than end time.";
19
}
20
if (!jobStatus.equalsIgnoreCase("NA"))
{
21
valid = false;
22
errorReason = " Job status shoud be 'NA' at first.";
23
}
24
if (valid)
{
25
return new Job(jobId, jobDescription, jobOwner, jobStartTime,
26
jobEndTime, jobStatus);
27
} else
{
28
throw new BuilderException(errorReason);
29
}
30
}
4. JobParser
1
/** *//**
2
* Parses the HashMap object to extract
3
* all kinds of imormation about a job.
4
* May use "default value" when exception
5
* happened.
6
*
7
* @param map the map
8
*/
9
public void parse(HashMap map)
{
10
if (map != null)
{
11
for (Iterator it = map.keySet().iterator(); it.hasNext();)
{
12
String key = (String) it.next();
13
String value = (String) map.get(key);
14
// Parse key/value pair
15
if (key.equalsIgnoreCase("id"))
{
16
builder.setJobId(new Long(value).longValue());
17
} else if (key.equalsIgnoreCase("description"))
{
18
builder.setJobDescription(value);
19
} else if (key.equalsIgnoreCase("owner"))
{
20
builder.setJobOwner(value);
21
} else if (key.equalsIgnoreCase("start"))
{
22
SimpleDateFormat sdf = new SimpleDateFormat(
23
"yyyy-MM-dd HH:mm:ss");
24
try
{
25
builder.setJobStartTime(sdf.parse(value));
26
} catch (ParseException pe)
{
27
builder.setJobStartTime(new Date());
28
}
29
30
} else if (key.equalsIgnoreCase("end"))
{
31
SimpleDateFormat sdf = new SimpleDateFormat(
32
"yyyy-MM-dd HH:mm:ss");
33
try
{
34
builder.setJobEndTime(sdf.parse(value));
35
} catch (ParseException pe)
{
36
builder.setJobEndTime(null);
37
}
38
39
} else if (key.equalsIgnoreCase("status"))
{
40
builder.setJobStatus(value);
41
} else
{
42
// Do nothing
43
}
44
}
45
}
46
}
那么如何使用Builder和Parser來創建對象呢?下面是JobBuilderTest的示例代碼
1
package org.pattern.build;
2
3
import java.util.HashMap;
4
5
public class JobBuilerTest
{
6
7
public static void main(String args[])
{
8
new JobBuilerTest().testJobBuilder();
9
}
10
11
/** *//**
12
* Test job builder.
13
*/
14
public void testJobBuilder()
{
15
// 此處省略了從客戶端獲取Job參數的過程,在真實的環境中這個過程往往是
16
// 通過GUI界面,由客戶輸入或選擇,在一步步的交互中完成。
17
18
// 此時Builder模式的好處就體現在它可以先將數據緩存在自己的內部,通過
19
// 解析器(Parser)對用戶的輸入進行逐步的解析,這特別適合于需要通過大量
20
// 的交互過程之后才能知道構建對象的所有最終屬性的情況(類似于Wizword)
21
22
// 這樣做的好處是確保到最后Builder模式構建出來的對象是可用的,有商業
23
// 意義的。如果直接采用new一個對象的方法雖然簡單,但在構造過程非常復雜
24
// 或長的情況下,除了會使目標類過于龐大之外,還可能出現當實例化一個對象
25
// 后卻發現該對象由于某些屬性的問題而無效或沒有商業意義。
26
27
// 采用了Builder模式之后由于有了一個緩存和解析的過程,可以在解析的過程
28
// 中規避各種錯誤,可以拋出異常。這樣就不會出現實例化了無用的對象而浪費
29
// 內存的缺點了。而且可以使目標類集中于各種業務操作不用關心對象的實例化
30
HashMap<String, String> map = new HashMap<String, String>();
31
map.put("id", "1");
32
map.put("description", "job for test");
33
map.put("owner", "paul");
34
map.put("start", "2007-12-26 12:00:00");
35
map.put("end", "2007-12-26 23:59:59");
36
map.put("status", "NA");
37
38
// Create a builder
39
Builder builder = new JobBuilder();
40
// Create a parser with the builder as it's parameter
41
JobParser parser = new JobParser(builder);
42
// Parse the job data
43
parser.parse(map);
44
// Return the job
45
try
{
46
Job job = (Job) builder.build();
47
System.out.println(job.toString());
48
} catch (BuilderException be)
{
49
// TODO Auto-generated catch block
50
be.printStackTrace();
51
}
52
}
53
54
}
55
總結:
1. 使用Builder的最佳場合:
使用Builder模式的最佳場合應該是:
對象的構建過程長或復雜、構建對象所需的全部參數無法在一開始就完全獲得,必須通過一步步的交互過程來獲取。例如:通過Web頁面的輸入或用戶選擇來構建所需對象
2. Builder模式的好處:
Builder模式的一個最重要的好處除了將對象構建的“部件”和“過程”解耦之外,還能夠保證我們構建出來的對象都是完整的,可用的,具有商業意義的。如果因為構建參數的缺少或錯誤而導致直接實例化一個對象后才發現對象是不可用的,會浪費系統的資源,使用Builder模式則可用避免這種情況。
附:BuilderPattern的源代碼
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。
posted on 2008-01-02 23:51
Paul Lin 閱讀(1620)
評論(2) 編輯 收藏 所屬分類:
模式與重構