- 浏览: 328205 次
- 性别:
- 来自: 杭州
-
文章分类
最新评论
-
arlenliugj:
才发现这贴子好早
如何在Ubuntu下安装windows7 -
arlenliugj:
请问一下,这样安装上windows会不会把已经装好的linux ...
如何在Ubuntu下安装windows7 -
zhaomengbin:
写的很不错,可以写个文件给合并的方法么?,将分割后的几份文件还 ...
文件分割程序 -
junhe0723:
3Q,刚出现这个问题解决了。
jvm terminated exit code 1 -
Anddy:
shell 双击选中太不智能了。
XSHELL快捷键设置
实际上,既然Builder和Factory同属创建型模式,那么他们的最大共同点就在于都可以创建类对象, 在这点上,不光这两个模式一样,其它创建型模式也一样。但正如在《深入探索Factory模式与Prototype模式的异同(续)》一文中所说,这些模 式,功能上的相似,只是“形似而非神似”。既然这样,那好,下面就让我们能看看Builder和Factory在功能的相似上,存在哪些神韵方面的差别。
首先,也是最重要的一点,就是虽然Builder和Factory都可创建产品,但两者所创建的产品类型完全不一样。Factory创建只能是单一的产品(单一在这指它非复合产品),而Builder所创建的产品是复合产品,即产品本身就是由其它部件产品组成的。
举 个例子来说,现在要生产一辆车,假设它就只由这三个部分组成:玻璃、轮子、发动机。对于工厂模式来说,他创建后返回的,只能是玻璃,或者轮子,抑或是发动 机。不管怎么样,他不能向客户返回一辆完整的汽车,要得到一辆完整的汽车,客户必须自己动手去把这些零部件组装成一辆汽车。从这个意义上来讲,工厂模式中 的工厂,只是充当了零件厂的角色。那Builder又是如何创建产品的呢?在Builder模式中,一般不需要、也不充许向客户返回单个部件,他向客户返 回的,仅仅就是一部已经完全组装好的汽车成品。对于汽车部件的生产细节,客户不需要、也不应该让他们知道。写到这,我突然想到了组装电脑与品牌电脑的差 别,组装电脑虽然价格便宜,且易于改动,但性能没有保证,另外你自己还必须了解很多有关电脑方面的知识;对于品牌电脑,价格贵这点先暂时不说,关键在于他 不灵活,但是它的性能可以得到很好保证(由厂家),这易像我们在Builder的系统端保证部件的质量一样。另外,对于品牌电脑,客户根本不需要了解多少 电脑组装方面的知识,就可以把一台电脑抱回家,开机使用了。那么,在实际运用中,你是喜欢做DIY一族呢,还是喜欢稳定有保证的质量呢?好像在我们编著程 的这个过程中,我们比较趋向于使用“品牌电脑”。这也就为我们正确使用这两种设计模式提供了一个方向:如果你要生产的产品是由不同部件组成的,你最好使用Builder模式,而非Factory模式。
另外,Builder和Factory的差别,就在于他们所生产部件 产品所在产品树的问题。这样说,可能有点拗口。具体来说吧,在工厂模式中,我们知道,一个工厂可以创建多个产品,但一个工厂模式中所创建的某个产品,都会 和另一个工厂中所创建的产品在同一棵继承树上。如果大家看过我最早写的《用Java实现的设计模式系列(1)—Factory 》那篇文章,就会记得,我在CFactoryMac中创建了一种产品叫MacRam,而在CFactoryWin中创建了另一种产品叫WinRam,很显 然,这两种产品是在同一棵继承树上的。对于它们之所以会出现在同一棵继承树上,是完全由Factory模式本身所决定的。大家如果看过Factory的 UMl图,就应该记得,要实现Factory模式,一定要有一个Abstract Product类,具体产品再由它派生出来。好了,说完了Factory,再让我们来看看Builder中是否必这么做!实际上,在Builder模式 中,我们只是在Abstract Builder中封装了创建部件的接口,而具体创建了什么部件,不同的实际Builder可能会生产出完全不一样的部件,这样不会存在任何问题,因为,我 上面说过,Builder只是向客户返回成品,而不向客户返回具体部件,这样,当然就允许产品的部件按要求随意变化了。
再举个例子吧,假如你现在要创建两种风马不相及的东西,例如一种是人,它就只由这几部分组成:脑、身、四肢;另一种是树,也由三个部分组成:根、叶、茎。
当构造某对象时,也许无法保证总能事先获知对象的所有所需信息。尤其是,有时候 目标对象的构造器的参数只能逐步获取,我们希望能够一步一步地构造目标对象,常见情况比如解析器和用户接口。或者,当对类的重点难以很好把握并且类的构造 过程相当复杂时,你也许希望简化类的规模,这时就可以使用Builder模式。
Builder模式的意图是把构造对象实例的代码逻辑移到要实例化的类的外部。
1. 常见的生成器:
使用Builder模式而获益的常见情况是定义所期望对象的数据被嵌套在文本字符串中。随着逐步查询代码或者说解析数据的过程中,你需要随着发现过程来保 存这些数据。不管解析器是基于XML的还是手工执行的,最初也许不足以拥有构造合法目标对象所需要的全部数据。对这种情况,Builder模式的处理方式 是把数据存储在临时对象中,直到程序拥有构造所需要的全部数据,这时候才查询存储的临时对象来构造目标对象。
假设除了生产焰火制品之外,Oozinoz公司偶尔还对外提供焰火表演服务。旅行社可以按照下面的模板向Oozinoz公司发送电子邮件申请预定焰火表演:
Date,November 5,Headcount,250,City,SprintField,
DollarPerHead,9.95,HasSite,False
可能你会猜到该协议一定是在XML之前诞生的。但是无论如何,该协议经实践证明的确是实用的。该预定请求说明预定焰火表演的时间和城市,以及最少来宾人数 和身强体壮来宾的服务费用。通过上面这封邮件,该旅行社告诉Oozinoz公司,将有250名来宾观看这次的焰火表演,并且该旅行社愿意为每位来宾支持 9。95美元,共计2487.50美元。另外,邮件还说明该旅行社还没有确定焰火表演的地点。
我们当前的任务就是解析这个文本性请求并创建一个Reservation对象来表示这次预定申请。为此,我们可以先创建一个属性为空的 Reservation对象,并在解析器解析该文本请求的过程中逐步设置Reservation对象的各种属性。然而,这种做法存在这样一个问题:由此创 建的Reservation对象并不一定能代表一次有效的焰火预定申请。例如,当我们的解析器解析完文本请求之后,可能会发现该文本请求没有说明表演日 期。
我们可以创建一个ReservationBuilder类,以保证所创建 的Reservation对象总能代表一次有效的焰火预定申请。该ReservationBuilder对象可以保存解析器解析出的各个预定请求属性,然 后再利用这些属性参数创建Reservation对象,随后验证该对象的有效性。下图给出了该设计所涉及的类。
生成器类将某个领域的类的构造逻辑提取出来。每当解析器解析出一个初始化参数,
就把该参数交给生成器类对象
ReservationBuilder类的build()方法是抽象方法,因而ReservationBuilder类是个抽象类。根据基于不完整数据创 建Reservation对象的方式不同,我们将构造非抽象的ReservationBuilder子类。ReservationParser类构造器把 生成器作为参数,并向之传递信息。parse()方法从预定字符串读取信息,并传递给生成器,代码如下所示:
public void parse(String s) throws ParseException
{
String[] tokens = s.split(",");
for(int i=0;i<tokens.length;i+=2)
{
String type = tokens[i];
String val = tokens[i+1];
if("date".compareToIgnoreCase(type) == 0)
{
Calendar now = Calendar.getInstance();
DateFormat formatter = DataFormat.getDateInstance();
Date d = formatter.parse( val + "," + now.get(Calendar.YEAR));
builder.setDate(ReservationBuilder.futurize(d));
}else if("headcount".compareToIgnoreCase(type) == 0)
builder.setHeadcount(Integer.parseInt(val));
else if("City".compareToIgnoreCase(type) == 0)
builder.setCity(val.trim());
else if("DollarPerHead".compareToIgnoreCase(type)==0)
builder.setDollarsPerHead(new Dollars(Double.parseDouble(val)));
else if("HasSite".compareToIgnoreCase(type)==0)
builder.setHasSite(val.equalsIgnoreCase("true"));
}
}
当发现"date"时,解析器就解析接下来的值,并把日期保存起来。futurize()方法把年份放在前面,这样可以保证日期"November 5"解析为11月5日。当读者查看代码时,也许会发现解析器可能会误入歧途,从预定字符串的最初标志位开始解析。
突破题:split(s)调用使用的正则表达式对象会把逗号分隔的列表分隔为多个独立的字符串。请考虑如何改进这种正则表达式或整个方法,保证解析器能够更好地识别预定信息。
答:让解析器更加灵活的一种方式是允许接收逗号后的多个空格。为实现这一点,split()调用模式如下:
s.split(",*");
或者通过像如下代码那样初始化Regex对象,可以得到任何类型的空格。
s.split(",\\s*");
\s字符表示正则表达式中的空格“字符类”。请注意,所有解决方案都假设这些字段内没有嵌套逗号。
为了让这个正则表达式更加灵活,你也许会怀疑整个方法。尤其需要注意的是,你也许希望旅行代理能以XML格式来发送预定信息。你也许要建立一套标记,以便于XML解析器使用。
2.根据约束构建对象:
在这个例子中,我们必须保证绝不会实例化一个无效的Reservation对象。具体而言,假定每个有效的焰火表演预定申请必须明确指出表演日期和城市。 另外,假定Oozinoz公司的商业规则规定每次焰火表演的观看人数必须大于或等于25人,或者总费用不得少于495.95美元。我们也许希望在数据库中 记录这些限制,但现在我们使用Java代码把这些信息记录为常量,代码如下所示:
public abstract class ReservationBuilder
{
public static final int MINHEAD = 25;
public static final Dollars MINTOTAL = new Dollars(495.95);
//...
}
观看人数太少或者收入太少的预定申请也会被视为无效的。为了避免在预定申请无效 的情况下构造Reservation实例,我们可以在Reservation的构造器或者其调用的init()方法中加入进行商业规则检查的代码以及抛出 异常的代码。但是,一旦创建了Reservation对象之后,这些商业规则就不会再被使用,它与Reservation对象的其他方法没有任何瓜葛。这 个时候,我们可以创建一个生成器,并把Reservation的构造逻辑移入该生成器中。这样,Reservation类仅包含除构造之外的其他方法,从 而变得更加简洁。另外,通过使用该生成器,我们还可以对Reservation对象的不同参数进行验证,并对无效参数做出相应处理。最后,通过将构造逻辑 移入ResevationBuilder子类中,可以根据解析器解析出的参数逐步构造Reservation对象。下图给出了由 ReservationBuilder类派生出的两个非抽象子类,这两个子类对无效参数的处理方式不同。
在根据给定的一组参数创建有效的对象的时候,生成器可能会遇到无效参数,这个
时候,有的生成器会抛出异常;有的生成器则会忽略
此图表更加清楚地说明了使用builder模式的好处:通过把构造逻辑和 Reservation类本身分离开,我们可以把构造过程作为一个独立的任务来实现,甚至可以创建独立的生成方法层次关系。生成器行为中的差异也许对预定 逻辑影响甚微。比如,上图的不同生成器区别在于是否抛出BuilderException异常。使用生成器的代码看起来如下代码所示:
package app.builder;
import com.oozinoz.reservation.*;
public class ShowUnforgiving
{
public static void main(String[] args)
{
String sample = "Date,November 5,Headcount,250,"
+"City,Springfield,DollarsPerHead,9.95,"
+"HasSite,False";
ReservationBuilder builder = new UnforgivingBuilder();
try
{
new ReservationParser(builder).parse(sample);
Reservation res = builder.build();
System.out.println("Unforgiving builder:"+res);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}
运行上述代码会输出一个Reservation对象:
Date,November 5,Headcount,250,City,SprintField,
Dollar/Head,9.95,Has Site,false
上面这个应用程序首先给定了一个预定请求字符串,接着实例化了一个生成器和一个解析器,随后开始利用解析器解析该字符串。读入该预定的请求字符串之后,该解析器便开始不断地将解析出来的预定申请信息通过生成器的set方法传给生成器。
预定请求字符串解析完毕之后,应用程序便用该生成器构造一个有效的预定对象。当出现异常的时候,该应用程序仅打印出该异常有关的文字信息。而在实际的应用中,当出现异常的时候,我们需要完成一些重要的异常处理工作。
突破题:当预定申请信息中的日期或者城市属性为空,或者观看人数太少,或者焰火表演的整场演出费用太低时,UnforgivingBuilder类的build()方法就会抛出BuilderException异常。请据此说明写出build()方法的实现。
答:若所有属性都是有效的,则build()方法就会返回有效的Reservation对象;否则,该方法就会抛出异常。下面是该方法的一种实现:
public Reservation build() throws BuilderException
{
if(date == null)
throw new BuilderException("Valid date not found");
if(city == null)
throws new BuilderException("Valid city not found");
if(headCount < MINHEAD)
throws new BuilderException("Minimum headcount is:"+ MINHEAD);
if(dollarsPerHead.times(headcount).isLessThen(MINTOTAL))
throws new BuilderException("Mininum total cost is" + MINTOTAL);
return new Reservation(
date,
headCount,
city,
dollarsPerHead,
hasSite);
}
ReservationBuilder超类定义常量MINHEAD和MINTOTAL。
如果这个生成器没有遇到问题,则会返回一个有效的Reservation对象。
3.根据不完整信息构造符合约束的对象:
UnforgivingBulder类将拒绝任何信息不完整的请求。公司可能会期望在客户的预定申请缺少某些信息的情况下,我们的软件系统能够对该系统进行适合的修改。
具体而言,假定申请中没有指明观看焰火的人数,分析人员会要求我们根据公司的商业规则为观看人数设置一个最小值。同样,如果预定申请中没有指明焰火表演的 单人费用,我们可以为之设置一个合适的费用,从而使得整场演出费达到该商业规则指定的最小值。这些需求相当简单,但是设计起来需要一些技巧。比如,如果预 定字符串提供单人费用数据值,但是没有提供总人数,生成器应该怎么办?
突破题:请写出ForgivingBuilder.build()方法的规范,说明当预定字符串没有提供总人数或者单人费用时,生成器应该怎么办?
答:像以前一样,如果预定活动没有指定城市或者日期,则程序会抛出异常,因为无法预测这些数据。如果缺少总人数或者人均费用,请注意以下几点:
(1)如果预定请求没有说明总人数和人均费用,则程序会自动把总人数设置为最小值,把人均费用设置为最小总费用除总人数。
(2)如果预定请求没有说明总人数,但是指定了人均费用值,则程序会把总人数自动设置为最小值,保证总费用足够维持本次活动。
(3)如果预定请求指定了总人数,但是没有指定人均费用,则程序会把人均费用设置为某个值,保证总费用足够维护本次活动。
突破题:请写出ForgivingBuilder类中build()方法的实现。
public Reservation build() throws BuilderException
{
boolean noHeadCount = (headCount == 0);
boolean noDollarsPerHead = (dollarsPerHead.isZero());
if(noHeadcount && noDollarsPerHead)
{
headCount = MINHEAD;
dollarsPerHead = sufficientDollars(headCount);
}else if(noHeadCount)
{
headCount = (int)Math.ceil(MINTOTAL.divideBy(dollarsPerHead));
headCount = Math.max(headcount,MINHEAD);
}else if(noDollarsPerHead)
{
dollarsPerHead = sufficientDollars(headCount);
}
check();
return new Reservation(
date,
headCount,
city,
dollarsPerHead,
hasSite);
}
上述代码依赖于check()方法,这个方法类似于UnforgivingBuilder类的build()方法。
protected void check() throws BuilderException
{
if(date == null)
throw new BuilderException("Valid date not found");
if(city == null)
throw new BuilderException("Valid city not found");
if(headcount<MINHEAD)
throw new BuilderException("Minimum headcount is "+MINHEAD);
if(dollarsPerHead.times(headcount).isLessThan(MINTOTAL))
throw new BuilderException("Minimum total cost is "+MINTOTAL);
}
ForgivingBuilder类和UnforginvBuilder类可以确保Reservation对象始终有效。当构造预定对象出现问题时,你的设计也应该提供足够的灵活性来解决出现的问题。
4.小结:
Builder模式将一个复杂对象的构造逻辑从其代码中分离出来。其直接的效果就是简化了原来复杂的目标对象。生成器类集中负责目标类对象的构造,而目标 类则集中完成有效实例的各种非构造操作。其中模式的一个突出优点体现在,我们在实例化目标类之前可以能够构造一个有效的对象,而且不必将这些构造逻辑放在 目标类的构造器中。另外,Builder模式还使得我们可以逐步构造目标类对象。这个特点使得Builder模式特别知县于通过解析文本获取对象信息,或 者从图形用户界面收集对象信息来创建对象的场合。
首先,也是最重要的一点,就是虽然Builder和Factory都可创建产品,但两者所创建的产品类型完全不一样。Factory创建只能是单一的产品(单一在这指它非复合产品),而Builder所创建的产品是复合产品,即产品本身就是由其它部件产品组成的。
举 个例子来说,现在要生产一辆车,假设它就只由这三个部分组成:玻璃、轮子、发动机。对于工厂模式来说,他创建后返回的,只能是玻璃,或者轮子,抑或是发动 机。不管怎么样,他不能向客户返回一辆完整的汽车,要得到一辆完整的汽车,客户必须自己动手去把这些零部件组装成一辆汽车。从这个意义上来讲,工厂模式中 的工厂,只是充当了零件厂的角色。那Builder又是如何创建产品的呢?在Builder模式中,一般不需要、也不充许向客户返回单个部件,他向客户返 回的,仅仅就是一部已经完全组装好的汽车成品。对于汽车部件的生产细节,客户不需要、也不应该让他们知道。写到这,我突然想到了组装电脑与品牌电脑的差 别,组装电脑虽然价格便宜,且易于改动,但性能没有保证,另外你自己还必须了解很多有关电脑方面的知识;对于品牌电脑,价格贵这点先暂时不说,关键在于他 不灵活,但是它的性能可以得到很好保证(由厂家),这易像我们在Builder的系统端保证部件的质量一样。另外,对于品牌电脑,客户根本不需要了解多少 电脑组装方面的知识,就可以把一台电脑抱回家,开机使用了。那么,在实际运用中,你是喜欢做DIY一族呢,还是喜欢稳定有保证的质量呢?好像在我们编著程 的这个过程中,我们比较趋向于使用“品牌电脑”。这也就为我们正确使用这两种设计模式提供了一个方向:如果你要生产的产品是由不同部件组成的,你最好使用Builder模式,而非Factory模式。
另外,Builder和Factory的差别,就在于他们所生产部件 产品所在产品树的问题。这样说,可能有点拗口。具体来说吧,在工厂模式中,我们知道,一个工厂可以创建多个产品,但一个工厂模式中所创建的某个产品,都会 和另一个工厂中所创建的产品在同一棵继承树上。如果大家看过我最早写的《用Java实现的设计模式系列(1)—Factory 》那篇文章,就会记得,我在CFactoryMac中创建了一种产品叫MacRam,而在CFactoryWin中创建了另一种产品叫WinRam,很显 然,这两种产品是在同一棵继承树上的。对于它们之所以会出现在同一棵继承树上,是完全由Factory模式本身所决定的。大家如果看过Factory的 UMl图,就应该记得,要实现Factory模式,一定要有一个Abstract Product类,具体产品再由它派生出来。好了,说完了Factory,再让我们来看看Builder中是否必这么做!实际上,在Builder模式 中,我们只是在Abstract Builder中封装了创建部件的接口,而具体创建了什么部件,不同的实际Builder可能会生产出完全不一样的部件,这样不会存在任何问题,因为,我 上面说过,Builder只是向客户返回成品,而不向客户返回具体部件,这样,当然就允许产品的部件按要求随意变化了。
再举个例子吧,假如你现在要创建两种风马不相及的东西,例如一种是人,它就只由这几部分组成:脑、身、四肢;另一种是树,也由三个部分组成:根、叶、茎。
当构造某对象时,也许无法保证总能事先获知对象的所有所需信息。尤其是,有时候 目标对象的构造器的参数只能逐步获取,我们希望能够一步一步地构造目标对象,常见情况比如解析器和用户接口。或者,当对类的重点难以很好把握并且类的构造 过程相当复杂时,你也许希望简化类的规模,这时就可以使用Builder模式。
Builder模式的意图是把构造对象实例的代码逻辑移到要实例化的类的外部。
1. 常见的生成器:
使用Builder模式而获益的常见情况是定义所期望对象的数据被嵌套在文本字符串中。随着逐步查询代码或者说解析数据的过程中,你需要随着发现过程来保 存这些数据。不管解析器是基于XML的还是手工执行的,最初也许不足以拥有构造合法目标对象所需要的全部数据。对这种情况,Builder模式的处理方式 是把数据存储在临时对象中,直到程序拥有构造所需要的全部数据,这时候才查询存储的临时对象来构造目标对象。
假设除了生产焰火制品之外,Oozinoz公司偶尔还对外提供焰火表演服务。旅行社可以按照下面的模板向Oozinoz公司发送电子邮件申请预定焰火表演:
Date,November 5,Headcount,250,City,SprintField,
DollarPerHead,9.95,HasSite,False
可能你会猜到该协议一定是在XML之前诞生的。但是无论如何,该协议经实践证明的确是实用的。该预定请求说明预定焰火表演的时间和城市,以及最少来宾人数 和身强体壮来宾的服务费用。通过上面这封邮件,该旅行社告诉Oozinoz公司,将有250名来宾观看这次的焰火表演,并且该旅行社愿意为每位来宾支持 9。95美元,共计2487.50美元。另外,邮件还说明该旅行社还没有确定焰火表演的地点。
我们当前的任务就是解析这个文本性请求并创建一个Reservation对象来表示这次预定申请。为此,我们可以先创建一个属性为空的 Reservation对象,并在解析器解析该文本请求的过程中逐步设置Reservation对象的各种属性。然而,这种做法存在这样一个问题:由此创 建的Reservation对象并不一定能代表一次有效的焰火预定申请。例如,当我们的解析器解析完文本请求之后,可能会发现该文本请求没有说明表演日 期。
我们可以创建一个ReservationBuilder类,以保证所创建 的Reservation对象总能代表一次有效的焰火预定申请。该ReservationBuilder对象可以保存解析器解析出的各个预定请求属性,然 后再利用这些属性参数创建Reservation对象,随后验证该对象的有效性。下图给出了该设计所涉及的类。
生成器类将某个领域的类的构造逻辑提取出来。每当解析器解析出一个初始化参数,
就把该参数交给生成器类对象
ReservationBuilder类的build()方法是抽象方法,因而ReservationBuilder类是个抽象类。根据基于不完整数据创 建Reservation对象的方式不同,我们将构造非抽象的ReservationBuilder子类。ReservationParser类构造器把 生成器作为参数,并向之传递信息。parse()方法从预定字符串读取信息,并传递给生成器,代码如下所示:
public void parse(String s) throws ParseException
{
String[] tokens = s.split(",");
for(int i=0;i<tokens.length;i+=2)
{
String type = tokens[i];
String val = tokens[i+1];
if("date".compareToIgnoreCase(type) == 0)
{
Calendar now = Calendar.getInstance();
DateFormat formatter = DataFormat.getDateInstance();
Date d = formatter.parse( val + "," + now.get(Calendar.YEAR));
builder.setDate(ReservationBuilder.futurize(d));
}else if("headcount".compareToIgnoreCase(type) == 0)
builder.setHeadcount(Integer.parseInt(val));
else if("City".compareToIgnoreCase(type) == 0)
builder.setCity(val.trim());
else if("DollarPerHead".compareToIgnoreCase(type)==0)
builder.setDollarsPerHead(new Dollars(Double.parseDouble(val)));
else if("HasSite".compareToIgnoreCase(type)==0)
builder.setHasSite(val.equalsIgnoreCase("true"));
}
}
当发现"date"时,解析器就解析接下来的值,并把日期保存起来。futurize()方法把年份放在前面,这样可以保证日期"November 5"解析为11月5日。当读者查看代码时,也许会发现解析器可能会误入歧途,从预定字符串的最初标志位开始解析。
突破题:split(s)调用使用的正则表达式对象会把逗号分隔的列表分隔为多个独立的字符串。请考虑如何改进这种正则表达式或整个方法,保证解析器能够更好地识别预定信息。
答:让解析器更加灵活的一种方式是允许接收逗号后的多个空格。为实现这一点,split()调用模式如下:
s.split(",*");
或者通过像如下代码那样初始化Regex对象,可以得到任何类型的空格。
s.split(",\\s*");
\s字符表示正则表达式中的空格“字符类”。请注意,所有解决方案都假设这些字段内没有嵌套逗号。
为了让这个正则表达式更加灵活,你也许会怀疑整个方法。尤其需要注意的是,你也许希望旅行代理能以XML格式来发送预定信息。你也许要建立一套标记,以便于XML解析器使用。
2.根据约束构建对象:
在这个例子中,我们必须保证绝不会实例化一个无效的Reservation对象。具体而言,假定每个有效的焰火表演预定申请必须明确指出表演日期和城市。 另外,假定Oozinoz公司的商业规则规定每次焰火表演的观看人数必须大于或等于25人,或者总费用不得少于495.95美元。我们也许希望在数据库中 记录这些限制,但现在我们使用Java代码把这些信息记录为常量,代码如下所示:
public abstract class ReservationBuilder
{
public static final int MINHEAD = 25;
public static final Dollars MINTOTAL = new Dollars(495.95);
//...
}
观看人数太少或者收入太少的预定申请也会被视为无效的。为了避免在预定申请无效 的情况下构造Reservation实例,我们可以在Reservation的构造器或者其调用的init()方法中加入进行商业规则检查的代码以及抛出 异常的代码。但是,一旦创建了Reservation对象之后,这些商业规则就不会再被使用,它与Reservation对象的其他方法没有任何瓜葛。这 个时候,我们可以创建一个生成器,并把Reservation的构造逻辑移入该生成器中。这样,Reservation类仅包含除构造之外的其他方法,从 而变得更加简洁。另外,通过使用该生成器,我们还可以对Reservation对象的不同参数进行验证,并对无效参数做出相应处理。最后,通过将构造逻辑 移入ResevationBuilder子类中,可以根据解析器解析出的参数逐步构造Reservation对象。下图给出了由 ReservationBuilder类派生出的两个非抽象子类,这两个子类对无效参数的处理方式不同。
在根据给定的一组参数创建有效的对象的时候,生成器可能会遇到无效参数,这个
时候,有的生成器会抛出异常;有的生成器则会忽略
此图表更加清楚地说明了使用builder模式的好处:通过把构造逻辑和 Reservation类本身分离开,我们可以把构造过程作为一个独立的任务来实现,甚至可以创建独立的生成方法层次关系。生成器行为中的差异也许对预定 逻辑影响甚微。比如,上图的不同生成器区别在于是否抛出BuilderException异常。使用生成器的代码看起来如下代码所示:
package app.builder;
import com.oozinoz.reservation.*;
public class ShowUnforgiving
{
public static void main(String[] args)
{
String sample = "Date,November 5,Headcount,250,"
+"City,Springfield,DollarsPerHead,9.95,"
+"HasSite,False";
ReservationBuilder builder = new UnforgivingBuilder();
try
{
new ReservationParser(builder).parse(sample);
Reservation res = builder.build();
System.out.println("Unforgiving builder:"+res);
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
}
运行上述代码会输出一个Reservation对象:
Date,November 5,Headcount,250,City,SprintField,
Dollar/Head,9.95,Has Site,false
上面这个应用程序首先给定了一个预定请求字符串,接着实例化了一个生成器和一个解析器,随后开始利用解析器解析该字符串。读入该预定的请求字符串之后,该解析器便开始不断地将解析出来的预定申请信息通过生成器的set方法传给生成器。
预定请求字符串解析完毕之后,应用程序便用该生成器构造一个有效的预定对象。当出现异常的时候,该应用程序仅打印出该异常有关的文字信息。而在实际的应用中,当出现异常的时候,我们需要完成一些重要的异常处理工作。
突破题:当预定申请信息中的日期或者城市属性为空,或者观看人数太少,或者焰火表演的整场演出费用太低时,UnforgivingBuilder类的build()方法就会抛出BuilderException异常。请据此说明写出build()方法的实现。
答:若所有属性都是有效的,则build()方法就会返回有效的Reservation对象;否则,该方法就会抛出异常。下面是该方法的一种实现:
public Reservation build() throws BuilderException
{
if(date == null)
throw new BuilderException("Valid date not found");
if(city == null)
throws new BuilderException("Valid city not found");
if(headCount < MINHEAD)
throws new BuilderException("Minimum headcount is:"+ MINHEAD);
if(dollarsPerHead.times(headcount).isLessThen(MINTOTAL))
throws new BuilderException("Mininum total cost is" + MINTOTAL);
return new Reservation(
date,
headCount,
city,
dollarsPerHead,
hasSite);
}
ReservationBuilder超类定义常量MINHEAD和MINTOTAL。
如果这个生成器没有遇到问题,则会返回一个有效的Reservation对象。
3.根据不完整信息构造符合约束的对象:
UnforgivingBulder类将拒绝任何信息不完整的请求。公司可能会期望在客户的预定申请缺少某些信息的情况下,我们的软件系统能够对该系统进行适合的修改。
具体而言,假定申请中没有指明观看焰火的人数,分析人员会要求我们根据公司的商业规则为观看人数设置一个最小值。同样,如果预定申请中没有指明焰火表演的 单人费用,我们可以为之设置一个合适的费用,从而使得整场演出费达到该商业规则指定的最小值。这些需求相当简单,但是设计起来需要一些技巧。比如,如果预 定字符串提供单人费用数据值,但是没有提供总人数,生成器应该怎么办?
突破题:请写出ForgivingBuilder.build()方法的规范,说明当预定字符串没有提供总人数或者单人费用时,生成器应该怎么办?
答:像以前一样,如果预定活动没有指定城市或者日期,则程序会抛出异常,因为无法预测这些数据。如果缺少总人数或者人均费用,请注意以下几点:
(1)如果预定请求没有说明总人数和人均费用,则程序会自动把总人数设置为最小值,把人均费用设置为最小总费用除总人数。
(2)如果预定请求没有说明总人数,但是指定了人均费用值,则程序会把总人数自动设置为最小值,保证总费用足够维持本次活动。
(3)如果预定请求指定了总人数,但是没有指定人均费用,则程序会把人均费用设置为某个值,保证总费用足够维护本次活动。
突破题:请写出ForgivingBuilder类中build()方法的实现。
public Reservation build() throws BuilderException
{
boolean noHeadCount = (headCount == 0);
boolean noDollarsPerHead = (dollarsPerHead.isZero());
if(noHeadcount && noDollarsPerHead)
{
headCount = MINHEAD;
dollarsPerHead = sufficientDollars(headCount);
}else if(noHeadCount)
{
headCount = (int)Math.ceil(MINTOTAL.divideBy(dollarsPerHead));
headCount = Math.max(headcount,MINHEAD);
}else if(noDollarsPerHead)
{
dollarsPerHead = sufficientDollars(headCount);
}
check();
return new Reservation(
date,
headCount,
city,
dollarsPerHead,
hasSite);
}
上述代码依赖于check()方法,这个方法类似于UnforgivingBuilder类的build()方法。
protected void check() throws BuilderException
{
if(date == null)
throw new BuilderException("Valid date not found");
if(city == null)
throw new BuilderException("Valid city not found");
if(headcount<MINHEAD)
throw new BuilderException("Minimum headcount is "+MINHEAD);
if(dollarsPerHead.times(headcount).isLessThan(MINTOTAL))
throw new BuilderException("Minimum total cost is "+MINTOTAL);
}
ForgivingBuilder类和UnforginvBuilder类可以确保Reservation对象始终有效。当构造预定对象出现问题时,你的设计也应该提供足够的灵活性来解决出现的问题。
4.小结:
Builder模式将一个复杂对象的构造逻辑从其代码中分离出来。其直接的效果就是简化了原来复杂的目标对象。生成器类集中负责目标类对象的构造,而目标 类则集中完成有效实例的各种非构造操作。其中模式的一个突出优点体现在,我们在实例化目标类之前可以能够构造一个有效的对象,而且不必将这些构造逻辑放在 目标类的构造器中。另外,Builder模式还使得我们可以逐步构造目标类对象。这个特点使得Builder模式特别知县于通过解析文本获取对象信息,或 者从图形用户界面收集对象信息来创建对象的场合。
发表评论
-
访问者模式
2011-02-21 17:25 950话说有一个银行,有三个窗口,但是每个窗口的智能都是一样的,即都 ... -
迭代器模式
2011-02-21 17:25 1006【迭代器模式】 迭代器可以顺序访问一个聚集中的元素而不必显露聚 ... -
装饰器模式
2011-02-21 17:25 1983浅谈装饰器模式 序: 今天,为了满足我们项目组长的愿望, ... -
解释器模式
2011-02-21 17:24 1073一、引子 其实没有什么好的例子引入解释器模式,因为它描述了 ... -
命令模式
2011-02-21 17:24 10331.意图: 将一个请求或操作封装到对象中。 2 ... -
策略模式
2011-02-21 17:23 904当我们掌握了Java的语法,当我们了解了面向对象的封装、继承、 ... -
状态模式
2011-02-21 17:23 11451,状态模式允许一个"对象"在其内部状态改 ... -
模板模式
2011-02-21 17:23 1197模板方法(Template Method)模式是GOF设计模式 ... -
备忘录模式 (下)
2011-02-21 17:22 2156一、什么是备忘录模式 Memento模式也叫备忘录模式 ... -
备忘录
2011-02-21 17:22 1108备忘录(Memento Pattern)模式 备忘录模式又叫 ... -
原型模式Prototype(深拷贝)
2011-02-21 17:21 18851、定义:原型模式就是 ... -
原型模式
2011-02-21 17:21 1098原型模式(Prototype Patter ... -
抽象工厂模式
2011-02-21 17:20 1378假如你要制作一个对话框控件,你希望这个对话框可以有不同的Loo ... -
工厂方法模式
2011-02-21 17:20 11621、工厂模式的核心思想及分类 工厂方法模式的作用是 ... -
构造者模式
2011-02-21 17:19 1163多种设计模式能够解决 ... -
亨元模式
2011-02-21 17:18 1159翻译为亨元模式,或直译为轻量级模式。所谓亨元,就是被其它对象共 ... -
责任链模式
2011-02-21 17:18 6672责任链模式(Chain of Resp ... -
代理模式
2011-02-21 17:17 882代理模式:给某一对象提供代理对象,并由代理对象控制具体对象的引 ... -
观察者模式
2011-02-21 17:17 1041size=9] 论坛上很多人都讲设计模式,也讲了很多设计模式, ... -
多线程下的单例模式(上)
2011-02-21 17:07 1094Abstract 在开发中,如果某个实例的 ...
相关推荐
生成器模式 生成器模式(Builder Pattern)是一种创造型设计模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建可以创建不同的表示。这种模式可以在以下情况下使用: 1. 当创建复杂对象的算法应该独立于...
生成器模式是一种设计模式,属于创建型模式,它允许我们分步骤构建复杂对象,而无需提前知道整个对象的完整结构。这种模式的核心在于延迟初始化,它使得我们可以根据需要逐步构建对象,而不是一次性创建所有部分。在...
生成器模式(Builder Pattern)是Java设计模式中的创建型模式之一,主要解决复杂对象的构建问题,通过将构造过程逐步分解,使得构造过程与表示分离,使得同样的构建过程可以创建不同的表示。这种模式通常用于创建...
生成器模式,也被称为构建器模式,是一种软件设计模式,主要用在对象的创建过程中,它将复杂的构建过程分解为一系列简单的步骤,使得构建过程可配置,并且可以独立于对象的表示进行。在C#中,生成器模式的实现通常...
使用生成器模式模拟实现IBM电脑的生产,其中IBM电脑的主要结构用如下表示: class IBM{ string monitor=”IBM的显示器”; string keyboard=”IBM的键盘”; string mouse=”IBM的鼠标”; Motherboard* MB; ...
JavaScript中的构造器模式,也称为生成器模式,是一种创建型设计模式,主要目的是为了创建对象。构造器模式在解决工厂模式无法识别对象类型的问题时,引入了特定的构造函数来创建具有特定特性的对象。 首先,让我们...
在《Head First Design Patterns》文中是这么介绍生成器模式的,“封装一个产品的构造过程,并允许按步骤构造”,感觉说得不是很清楚。而在网上查找相关资料是这么解释的,建造者模式(Builder Pattern)使用多个简单...
生成器模式(Builder Pattern)是一种设计模式,它允许我们分步骤构建复杂对象,而无需暴露其构造过程。这种模式在创建对象时提供了更大的灵活性,特别是当构造过程需要多个步骤或者对象有不同的构造方式时。Builder...
在iOS开发中,生成器模式(Builder Pattern)是一种设计模式,它允许我们分步骤构建复杂的对象,而无需暴露创建过程的复杂性。这种模式在处理需要多种构建方式或需要逐步构造对象的情况时特别有用。标题“iOS 生成器...
本文主要介绍了C#设计模式之Builder生成器模式解决带老婆配置电脑问题,简单介绍了生成器模式的概念、功能并结合具体实例形式分析了C#生成器模式解决配电脑问题的步骤与相关操作技巧。 一、 Builder生成器模式定义 ...
生成器模式是设计模式中的一种,它属于创建型模式,主要用来处理复杂对象的构建过程。在这个“iOS生成器模式”中,我们将深入探讨如何在iOS开发中应用这个模式。 生成器模式的核心思想是将对象的创建过程和对象本身...
生成器模式(Builder Pattern)属于创建型设计模式,其核心在于将一个复杂对象的构建与它的表示分离,从而使同样的构建过程可以创建不同的表示。在软件开发中,生成器模式常用于创建一个复杂对象,其包含多个部件,...
生成器模式,也称为建造者模式,是一种设计模式,用于创建复杂对象,它允许你按照一系列步骤来构造对象,而这些步骤可以通过不同的实现来产生不同形式的对象。生成器模式的核心在于分离了对象的构造过程和表示细节,...
3 生成器模式.txt
Java 生成器模式详解 在软件开发中,经常需要创建复杂对象,而这些对象的创建过程往往涉及多个步骤和组件。为了解决这种问题,设计模式中引入了生成器模式(Builder Pattern)。生成器模式是一种创建型模式,它将...
在这里与各位分享本人从网络上下载的C#面向对象设计模式纵横谈系列视频,共有25节,除了第一节需要各位贡献一点资源分以作为对本人上传资源的回馈,后面的其他资源均不需要... 这是第4节:创建型模式Builder生成器模式
在众多的设计模式中,构造器模式(也称为生成器模式)是实现面向对象编程的一种基本方式。在构造器模式中,构造函数用于创建特定的对象,每个新创建的实例都继承构造函数的属性和方法。 构造器模式的核心概念是利用...
生成器模式是一种创建型设计模式,它允许我们分步骤构建复杂对象,同时隐藏了对象组装的细节。这种模式的核心思想是将产品的构造过程与产品本身分离,使构造过程可以有不同的表示方式。生成器模式通常由四个主要角色...
通过这种方式,生成器模式允许我们分离产品的构造和表示,提供了更灵活的构造过程。 首先,让我们深入理解生成器模式的核心概念。生成器模式通常由四个组件构成:Director(导演)、ConcreteBuilder(具体生成器)...