设计模式总结-Builder模式(二)
前面我们总结了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 * 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}
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 }
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 }
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
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的源代码
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要尽力打好一手烂牌。