- 浏览: 328188 次
- 性别:
- 来自: 杭州
-
文章分类
最新评论
-
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 1162多种设计模式能够解决 ... -
亨元模式
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 1040size=9] 论坛上很多人都讲设计模式,也讲了很多设计模式, ... -
多线程下的单例模式(上)
2011-02-21 17:07 1093Abstract 在开发中,如果某个实例的 ...
相关推荐
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
MMC整流器技术解析:基于Matlab的双闭环控制策略与环流抑制性能研究,Matlab下的MMC整流器技术文档:18个子模块,双闭环控制稳定直流电压,环流抑制与最近电平逼近调制,优化桥臂电流波形,高效并网运行。,MMC整流器(Matlab),技术文档 1.MMC工作在整流侧,子模块个数N=18,直流侧电压Udc=25.2kV,交流侧电压6.6kV 2.控制器采用双闭环控制,外环控制直流电压,采用PI调节器,电流内环采用PI+前馈解耦; 3.环流抑制采用PI控制,能够抑制环流二倍频分量; 4.采用最近电平逼近调制(NLM), 5.均压排序:电容电压排序采用冒泡排序,判断桥臂电流方向确定投入切除; 结果: 1.输出的直流电压能够稳定在25.2kV; 2.有功功率,无功功率稳态时波形稳定,有功功率为3.2MW,无功稳定在0Var; 3.网侧电压电流波形均为对称的三相电压和三相电流波形,网侧电流THD=1.47%<2%,符合并网要求; 4.环流抑制后桥臂电流的波形得到改善,桥臂电流THD由9.57%降至1.93%,环流波形也可以看到得到抑制; 5.电容电压能够稳定变化 ,工作点关键词:MMC
Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构,Simulink建模,MPPT最大功率点追踪,扰动观察法采用功率反馈方式,若ΔP>0,说明电压调整的方向正确,可以继续按原方向进行“干扰”;若ΔP<0,说明电压调整的方向错误,需要对“干扰”的方向进行改变。 ,Boost升压;光伏并网结构;Simulink建模;MPPT最大功率点追踪;扰动观察法;功率反馈;电压调整方向。,光伏并网结构中Boost升压MPPT控制策略的Simulink建模与功率反馈扰动观察法
STM32F103C8T6 USB寄存器开发详解(12)-键盘设备
科技活动人员数专指直接从事科技活动以及专门从事科技活动管理和为科技活动提供直接服务的人员数量
Matlab Simulink仿真探究Flyback反激式开关电源性能表现与优化策略,Matlab Simulink仿真探究Flyback反激式开关电源的工作机制,Matlab Simulimk仿真,Flyback反激式开关电源仿真 ,Matlab; Simulink仿真; Flyback反激式; 开关电源仿真,Matlab Simulink在Flyback反激式开关电源仿真中的应用
基于Comsol的埋地电缆电磁加热计算模型:深度解析温度场与电磁场分布学习资料与服务,COMSOL埋地电缆电磁加热计算模型:温度场与电磁场分布的解析与学习资源,comsol 埋地电缆电磁加热计算模型,可以得到埋地电缆温度场及电磁场分布,提供学习资料和服务, ,comsol;埋地电缆电磁加热计算模型;温度场分布;电磁场分布;学习资料;服务,Comsol埋地电缆电磁加热模型:温度场与电磁场分布学习资料及服务
1、文件内容:ibus-table-chinese-yong-1.4.6-3.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ibus-table-chinese-yong-1.4.6-3.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊
基于51单片机protues仿真的汽车智能灯光控制系统设计(仿真图、源代码) 一、设计项目 根据本次设计的要求,设计出一款基于51单片机的自动切换远近光灯的设计。 技术条件与说明: 1. 设计硬件部分,中央处理器采用了STC89C51RC单片机; 2. 使用两个灯珠代表远近光灯,感光部分采用了光敏电阻,因为光敏电阻输出的是电压模拟信号,单片机不能直接处理模拟信号,所以经过ADC0832进行转化成数字信号; 3. 显示部分采用了LCD1602液晶,还增加按键部分电路,可以选择手自动切换远近光灯; 4. 用超声模块进行检测距离;
altermanager的企业微信告警服务
MyAgent测试版本在线下载
Comsol技术:可调BIC应用的二氧化钒VO2材料探索,Comsol模拟二氧化钒VO2的可调BIC特性研究,Comsol二氧化钒VO2可调BIC。 ,Comsol; 二氧化钒VO2; 可调BIC,Comsol二氧化钒VO2材料:可调BIC技术的关键应用
C++学生成绩管理系统源码
基于Matlab与Cplex的激励型需求响应模式:负荷转移与电价响应的差异化目标函数解析,基于Matlab与CPLEX的激励型需求响应负荷转移策略探索,激励型需求响应 matlab +cplex 激励型需求响应采用激励型需求响应方式对负荷进行转移,和电价响应模式不同,具体的目标函数如下 ,激励型需求响应; matlab + cplex; 负荷转移; 目标函数。,Matlab与Cplex结合的激励型需求响应模型及其负荷转移策略
scratch介绍(scratch说明).zip
内容概要:本文全面介绍了深度学习模型的概念、工作机制和发展历程,详细探讨了神经网络的构建和训练过程,包括反向传播算法和梯度下降方法。文中还列举了深度学习在图像识别、自然语言处理、医疗和金融等多个领域的应用实例,并讨论了当前面临的挑战,如数据依赖、计算资源需求、可解释性和对抗攻击等问题。最后,文章展望了未来的发展趋势,如与量子计算和区块链的融合,以及在更多领域的应用前景。 适合人群:对该领域有兴趣的技术人员、研究人员和学者,尤其适合那些希望深入了解深度学习原理和技术细节的读者。 使用场景及目标:①理解深度学习模型的基本原理和结构;②了解深度学习模型的具体应用案例;③掌握应对当前技术挑战的方向。 阅读建议:文章内容详尽丰富,读者应在阅读过程中注意理解各个关键技术的概念和原理,尤其是神经网络的构成及训练过程。同时也建议对比不同模型的特点及其在具体应用中的表现。
该文档提供了一个关于供应链管理系统开发的详细指南,重点介绍了项目安排、技术实现和框架搭建的相关内容。 文档分为以下几个关键部分: 项目安排:主要步骤包括搭建框架(1天),基础数据模块和权限管理(4天),以及应收应付和销售管理(5天)。 供应链概念:供应链系统的核心流程是通过采购商品放入仓库,并在销售时从仓库提取商品,涉及三个主要订单:采购订单、销售订单和调拨订单。 大数据的应用:介绍了数据挖掘、ETL(数据抽取)和BI(商业智能)在供应链管理中的应用。 技术实现:讲述了DAO(数据访问对象)的重用、服务层的重用、以及前端JS的继承机制、jQuery插件开发等技术细节。 系统框架搭建:包括Maven环境的配置、Web工程的创建、持久化类和映射文件的编写,以及Spring配置文件的实现。 DAO的需求和功能:供应链管理系统的各个模块都涉及分页查询、条件查询、删除、增加、修改操作等需求。 泛型的应用:通过示例说明了在Java语言中如何使用泛型来实现模块化和可扩展性。 文档非常技术导向,适合开发人员参考,用于构建供应链管理系统的架构和功能模块。
这份长达104页的手册由清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室的余梦珑博士后及其团队精心编撰,内容详尽,覆盖了从基础概念、技术原理到实战案例的全方位指导。它不仅适合初学者快速了解DeepSeek的基本操作,也为有经验的用户提供了高级技巧和优化策略。
主题说明: 1、将mxtheme目录放置根目录 | 将mxpro目录放置template文件夹中 2、苹果cms后台-系统-网站参数配置-网站模板-选择mxpro 模板目录填写html 3、网站模板选择好之后一定要先访问前台,然后再进入后台设置 4、主题后台地址: MXTU MAX图图主题,/admin.php/admin/mxpro/mxproset admin.php改成你登录后台的xxx.php 5、首页幻灯片设置视频推荐9,自行后台设置 6、追剧周表在视频数据中,节目周期添加周一至周日自行添加,格式:一,二,三,四,五,六,日
运行GUI版本,可二开