前面我們總結(jié)了Builder模式的構(gòu)成、簡(jiǎn)單的使用例子,現(xiàn)在我們來(lái)看Builder模式的一個(gè)完整例子,該例子模擬了一個(gè)后臺(tái)定時(shí)運(yùn)行Job的構(gòu)建過(guò)程:
一、需求
用戶可以創(chuàng)建一個(gè)定時(shí)運(yùn)行的Job,該Job具有:任務(wù)Id,創(chuàng)建者,開(kāi)始時(shí)間,結(jié)束時(shí)間,任務(wù)狀態(tài)。
二、基本結(jié)構(gòu)
下面我們來(lái)看一張圖,從這張圖我們可以了解整個(gè)程序及Builder模式的構(gòu)成
計(jì)模式/package.JPG)
我們可以看到在這個(gè)包圖中,有4個(gè)比較重要的類(lèi)、接口:
1. Builder:包含了一個(gè)抽象的方法build
2. JobBuilder:實(shí)現(xiàn)了Builder接口,在其build方法中實(shí)現(xiàn)了Job對(duì)象的構(gòu)建。注意其中包含了Job對(duì)象所有屬性的一份拷貝,這是為了保存臨時(shí)緩存數(shù)據(jù)用的
3. Job:定時(shí)任務(wù)類(lèi),包含了一個(gè)package access權(quán)限的構(gòu)造方法和一系列g(shù)et方法,要注意的是沒(méi)有set方法
4.JobParser:Job解析器,用于解析用戶從GUI界面輸入的各種文本參數(shù)
好,下面我們?cè)賮?lái)看一張圖,看看這些類(lèi)之間到底是怎么合作的:
計(jì)模式/dependce.JPG)
可以看到JobParser類(lèi)會(huì)在解析構(gòu)建參數(shù)的過(guò)程中不斷調(diào)用JobBuilder類(lèi)的set方法,將解析后的臨時(shí)數(shù)據(jù)保存到Builder的緩存變量中,而當(dāng)解析完成后Builder就會(huì)從自身中取出所有的臨時(shí)數(shù)據(jù),用來(lái)構(gòu)建最終的目標(biāo)對(duì)象。這個(gè)過(guò)程中最終構(gòu)建的參數(shù)可能由于校驗(yàn)而發(fā)生了變化(例如:對(duì)缺少變量的默認(rèn)賦值)。
三、代碼示例
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接口是代表了一個(gè)生成器,它通過(guò)其build方法生成一個(gè)對(duì)象
6
* 然后返回給調(diào)用者
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來(lái)創(chuàng)建對(duì)象呢?下面是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參數(shù)的過(guò)程,在真實(shí)的環(huán)境中這個(gè)過(guò)程往往是
16
// 通過(guò)GUI界面,由客戶輸入或選擇,在一步步的交互中完成。
17
18
// 此時(shí)Builder模式的好處就體現(xiàn)在它可以先將數(shù)據(jù)緩存在自己的內(nèi)部,通過(guò)
19
// 解析器(Parser)對(duì)用戶的輸入進(jìn)行逐步的解析,這特別適合于需要通過(guò)大量
20
// 的交互過(guò)程之后才能知道構(gòu)建對(duì)象的所有最終屬性的情況(類(lèi)似于Wizword)
21
22
// 這樣做的好處是確保到最后Builder模式構(gòu)建出來(lái)的對(duì)象是可用的,有商業(yè)
23
// 意義的。如果直接采用new一個(gè)對(duì)象的方法雖然簡(jiǎn)單,但在構(gòu)造過(guò)程非常復(fù)雜
24
// 或長(zhǎng)的情況下,除了會(huì)使目標(biāo)類(lèi)過(guò)于龐大之外,還可能出現(xiàn)當(dāng)實(shí)例化一個(gè)對(duì)象
25
// 后卻發(fā)現(xiàn)該對(duì)象由于某些屬性的問(wèn)題而無(wú)效或沒(méi)有商業(yè)意義。
26
27
// 采用了Builder模式之后由于有了一個(gè)緩存和解析的過(guò)程,可以在解析的過(guò)程
28
// 中規(guī)避各種錯(cuò)誤,可以拋出異常。這樣就不會(huì)出現(xiàn)實(shí)例化了無(wú)用的對(duì)象而浪費(fèi)
29
// 內(nèi)存的缺點(diǎn)了。而且可以使目標(biāo)類(lèi)集中于各種業(yè)務(wù)操作不用關(guān)心對(duì)象的實(shí)例化
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
總結(jié):
1. 使用Builder的最佳場(chǎng)合:
使用Builder模式的最佳場(chǎng)合應(yīng)該是:
對(duì)象的構(gòu)建過(guò)程長(zhǎng)或復(fù)雜、構(gòu)建對(duì)象所需的全部參數(shù)無(wú)法在一開(kāi)始就完全獲得,必須通過(guò)一步步的交互過(guò)程來(lái)獲取。例如:通過(guò)Web頁(yè)面的輸入或用戶選擇來(lái)構(gòu)建所需對(duì)象
2. Builder模式的好處:
Builder模式的一個(gè)最重要的好處除了將對(duì)象構(gòu)建的“部件”和“過(guò)程”解耦之外,還能夠保證我們構(gòu)建出來(lái)的對(duì)象都是完整的,可用的,具有商業(yè)意義的。如果因?yàn)闃?gòu)建參數(shù)的缺少或錯(cuò)誤而導(dǎo)致直接實(shí)例化一個(gè)對(duì)象后才發(fā)現(xiàn)對(duì)象是不可用的,會(huì)浪費(fèi)系統(tǒng)的資源,使用Builder模式則可用避免這種情況。
附:BuilderPattern的源代碼
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。
posted on 2008-01-02 23:51
Paul Lin 閱讀(1621)
評(píng)論(2) 編輯 收藏 所屬分類(lèi):
模式與重構(gòu)