Java语言工场要领创建性模式先容
副标题#e#
正如同笔者在<简朴工场模式>一节里先容的,工场模式有简朴工场模式,工场要领模式和抽象工场模式几种形态。简朴工场模式已经在前面作过先容。在简朴工场模式中,一个工场类处于对产物类实例化挪用的中心位置上,它抉择那一个产物类该当被实例化, 如同一个交通警员站在交往的车辆流中,抉择放行那一个偏向的车辆向那一个偏向活动一样。
而本节要接头的工场要领模式是简朴工场模式的进一步抽象化和推广。它比简朴工场模式智慧的处地址于, 它不再作为一个详细的交通警员的面孔呈现,而是以交通警员局的面孔呈现。它把详细的车辆交通交给下面去打点。换言之,工场要领模式里不再只由一个工场类抉择那一个产物类该当被实例化,这个抉择被交给子类去作。处于工场要领模式的中心位置上的类甚至都不去打仗那一个产物类该当被实例化这种细节。这种进一步抽象化的功效,是这种新的模式可以用来处理惩罚越发巨大的景象。
为什么需要工场要领模式
此刻,让我们继承考查我们的小花果园。在<简朴工场模式>一节里,我们在后花圃里引进了水果类植物, 结构了简朴工场模式来处理惩罚, 利用一个FruitGardener类来认真创建水果类的实例。见下图。
图1. 简朴工场模式。FruitGardener把握所有水果类的生杀大权。
在这一节里,我们筹备再次引进蔬菜类植物,好比
西红柿 (Tomato)
土豆 (Potato)
西芥兰花 (Broccoli)
蔬菜与花和水果虽然有配合点,可又有差异之处。蔬菜需要喷洒(dust)杀虫剂(pesticide)除虫, 差异的蔬菜需要喷洒差异的杀虫剂,等等。怎么办呢?
那么,再借用一下简朴工场模式不就行了? 再设计一个专管蔬菜类植物的工场类,好比
图2. 简朴工场模式。VeggieGardener把握所有蔬菜类的生杀大权
这样做一个明明的不敷点就是不足一般化和抽象化。在FruitGardener和VeggieGardener类之间明明存在许多配合点, 这些配合点该当抽出来一般化和框架化。这样一来,假如后花圃的主人抉择再在园子里引进些树木类植物时, 我们有框架化的处理惩罚要领。本节所要引入的工场要领模式就切合这样的要求。
简朴工场模式的回首
有须要首先回首一下简朴工场模式的界说,以便于较量。
图3. 简朴工场模式的类图界说
从上图可以看出,简朴工场模式涉及到以下的脚色
工场类 (Creator)
接受这个脚色的是工场要领模式的焦点,是与应用措施细密相关的,直接在应用措施挪用下,创建产物实例的谁人类。
工场类只有一个,并且是实的。见下面的位图
#p#副标题#e#
产物 (Product)
接受这个脚色的类是工场要领模式所创建的工具的父类,或它们配合拥有的接口。
实产物 (Concrete Product)
接受这个脚色的类是工场要领模式所创建的任何工具所属的类。
实产物类可以是漫衍在一维数轴上的分立点 1,2,3,…中的任何一个,见下面的位图
工场要领模式的界说
我们给出工场要领模式的类图界说如下。
图4. 工场要领模式的类图界说
从上图可以看出,工场要领模式涉及到以下的脚色
抽象工场接口(Creator)
接受这个脚色的是工场要领模式的焦点,它是与应用措施无关的。任安在模式中创建工具的工场类必需实现这个接口。
实工场类 (Conrete Creator)
接受这个脚色的是与应用措施细密相关的,直接在应用措施挪用下,创建产物实例的那样一些类。
实工场类可以是漫衍在一维数轴上的分立点 1,2,3,…中的任何一个,见下面的位图
产物 (Product)
接受这个脚色的类是工场要领模式所创建的工具的父类,或它们配合拥有的接口。
实产物 (Concrete Product)
接受这个脚色的类是工场要领模式所创建的任何工具所属的类。
实产物类可以是漫衍在二维平面上的分立点 (1,1), (1,2), (2,3),…中的任何一个,见下面的位图
由实工场1(横数轴上第一点)创建的工具来自实产物类(1,1), (1,2), (1,3),…。由实工场2(横数轴上第二点)创建的工具来自实产物类(2,1), (2,2), (3,3),…。依此类推
#p#分页标题#e#
工场要领模式和简朴工场模式在界说上的差异是很明明的。工场要领模式的焦点是一个抽象工场类,而不像简朴工场模式, 把焦点放在一个实类上。工场要领模式可以答允许多实的工场类从抽象工场类担任下来, 从而可以在实际上成为多个简朴工场模式的综合,从而推广了简朴工场模式。
反过来讲,简朴工场模式是由工场要领模式退化而来。设想假如我们很是确定一个系统只需要一个实的工场类, 那么就不妨把抽象工场类归并到实的工场类中去。而这样一来,我们就退化到简朴工场模式了。
与简朴工场模式中的景象一样的是,ConcreteCreator 的factory() 要领返还的数据范例是一个接口 PlantIF,而不是哪一个详细的产物类。这种设计使得工场类创建哪一个产物类的实例细节完全封装在工场类内部。
工场要领模式又叫多形性工场模式,显然是因为实工场类都有配合的接口,可能都有配合的抽象父类。
工场要领模式在小花果园系统中的实现
好了,此刻让我们回到小花果园的系统里,看一看奈何发挥工场要领模式的威力,办理需要接连不绝向小花果园引进差异类此外植物所带来的问题。
首先,我们需要一个抽象工场类,好比叫做 Gardener,作为两个实工场类 FruitGardener 及 VeggieGardener 的父类。 Gardener 的 factory() 要领可以是抽象的,留给子类去实现,也可以是实的,在父类实现一部门成果,再在子类实现剩余的成果。我们选择将 factory() 做成抽象的,主要是因为我们的系统是一个示范系统,内容十分简朴,没有要在父类实现的任何成果。
图5. 工场要领模式在小花果园系统中的实现
抽象工场类 Gardener 是工场要领模式的焦点,可是它并不把握水果类或蔬菜类的生杀大权。相反地,这项权力被交给子类,即 VeggieGardener 及 FruitGardener。
package com.javapatterns.factorymethod;
abstract public class Gardener
{
public abstract PlantIF factory(String which) throws BadFruitException;
}
代码清单1. 父类 Gardener。
package com.javapatterns.factorymethod;
public class VeggieGardener extends Gardener
{
public PlantIF factory(String which) throws BadPlantException
{
if (which.equalsIgnoreCase("tomato"))
{
return new Tomato();
}
else if (which.equalsIgnoreCase("potato"))
{
return new Potato();
}
else if (which.equalsIgnoreCase("broccoli"))
{
return new Broccoli();
}
else
{
throw new BadPlantException("Bad veggie request");
}
}
}
代码清单2. 子类 VeggieGardener。
package com.javapatterns.factorymethod;
public class FruitGardener extends Gardener
{
public PlantIF factory(String which)
{
if (which.equalsIgnoreCase("apple"))
{
return new Apple();
}
else if (which.equalsIgnoreCase("strawberry"))
{
return new Strawberry();
}
else if (which.equalsIgnoreCase("grape"))
{
return new Grape();
}
else
{
throw new BadPlantException("Bad fruit request");
}
}
}
代码清单3. 子类 FruitGardener。
package com.javapatterns.factorymethod;
public class Broccoli implements VeggieIF, PlantIF
{
public void grow()
{
log("Broccoli is growing...");
}
public void harvest()
{
log("Broccoli has been harvested.");
}
public void plant()
{
log("Broccoli has been planted.");
}
private static void log(String msg)
{
System.out.println(msg);
}
public void pesticideDust(){ }
}
代码清单4. 蔬菜类 Broccoli。其它的蔬菜类与 Broccoli 相似,因此不再赘述。
package com.javapatterns.factorymethod;
public class Apple implements FruitIF, PlantIF
{
public void grow()
{
log("Apple is growing...");
}
public void harvest()
{
log("Apple has been harvested.");
}
public void plant()
{
log("Apple has been planted.");
}
private static void log(String msg)
{
System.out.println(msg);
}
public int getTreeAge(){ return treeAge; }
public void setTreeAge(int treeAge){ this.treeAge = treeAge; }
private int treeAge;
}
#p#分页标题#e#
代码清单5. 水果类 Apple。与<简朴工场模式>一节里的Apple类对比,独一的区别就是多实现了一个接口 PlantIF。其它的水果类与 Apple 相似,因此不再赘述。
package com.javapatterns.factorymethod;
public class BadPlantException extends Exception
{
public BadPlantException(String msg)
{
super(msg);
}
}
代码清单6. 破例类 BadPlantException。
工场要领模式应该在什么环境下利用
既然工场要领模式与简朴工场模式的区别极端微妙,那么应该在什么环境下利用工场要领模式,又应该在什么环境下利用简朴工场模式呢?
一般来说,假如你的系统不能事先确定那一个产物类在哪一个时刻被实例化,从而需要将实例化的细节局域化,并封装起来以支解实例化及利用实例的责任时,你就需要思量利用某一种形式的工场模式。
在我们的小花果园系统里,我们必需假设水果的种类随时都有大概变革。我们必需可以或许在引入新的水果品种时,可以或许很少窜改措施,就可以适应变革今后的环境。因此,我们显然需要某一种形式的工场模式。
假如在发明系统只用一个产物类品级(hierarchy)就可以描写所有已有的产物类,以及可预见的将来大概引进的产物类时,简朴工场模式是很好的办理方案。因为一个单一产物类品级只需要一个单一的实的工场类。
然而,当发明系统只用一个产物类品级不敷以描写所有的产物类,包罗今后大概要添加的新的产物类时,就该当思量回收工场要领模式。由于工场要领模式可以容很多个实的工场类,以每一个工场类认真每一个产物类品级,因此这种模式可以容纳所有的产物品级。
在我们的小花果园系统里,不但有水果种类的植物,并且有蔬菜种类的植物。换言之,存在不止一个产物类品级。并且产物类品级的数目也随时都有大概变革。因此,简朴工场模式不能满意需要,为办理向题,我们显然需要工场要领模式。
关于模式的实现
在实现工场要领模式时,有下面一些值得接头的处所。
第一丶在图四的类图界说中,可以对抽象工场(Creator) 做一些变通。变通的种类有
抽象工场(Creator) 不是接口而是抽象类。一般而言,抽象类不提供一个缺省的工场要领。 这样可以有效地办理奈何实例化事先不能预知的类的问题。
抽象工场(Creator) 自己是一个实类,并提供一个缺省的工场要领。 这样当最初的设计者所预见的实例化不能满意需要时,厥后的设计人员就可以用实工场类的factory() 要领来置换(Override))父类中factory()要领。
第二丶在经典的工场要领模式中,factory()要领是没有参量的。在本文举例时插手了参量,这实际上也是一种变通。
第三丶在给相关的类和要领取名字时,该当留意让别人一看即知你是在利用工场模式。
COM技能架构中的工场要领模式
在微软(Microsoft)所倡导的COM(Component Object Model)技能架构中, 工场要领模式起着要害的浸染。
在COM架框里,Creator接口的脚色是由一个叫作IClassFactory的COM接口来接受的。而实类ConcreteCreator的脚色是由实现IClassFactory接口的类CFactory(见下图)来接受的。一般而言,工具的创建大概要求分派系统资源,要求在差异的工具之间举办协调等等。因为IClassFactory的引进,所有这些在工具的创建进程中呈现的细节问题, 都可以封装在一个实现IClassFactory接口的实的工场类内里。这样一来, 一个COM架构的支持系统只需要创建这个工场类CFactory的实例就可以了。
图6. 微软(Microsoft)的COM(Component Object Model)技能架构是奈何事情的。
在上面的序列勾当(Sequence Activity)图中,用户端挪用COM的库函数CoCreateInstance。 CoCreateInstance在COM架框中以CoGetClassObject实现。 CoCreateInstance会在视窗系统的Registry里搜寻所要的部件(在我们的例子中即CEmployee)。假如找到了这个部件,就会加载支持此部件的DLL。当此DLL加载乐成后, CoGetClassObject就会挪用DllGetClassObject。后者利用new操纵符将工场类CFactory实例化。
#p#分页标题#e#
下面,DllGetClassObject会向工场类CFactory搜询IClassFactory接口,返还给CoCreateInstance。 CoCreateInstance接下来操作IClassFactory接口挪用CreateInstance函数。此时,IClassFactory::CreateInstance挪用new操纵符来创建所要的部件(CEmployee)。另外,它搜询IEmployee接口。在拿到接口的指针后, CoCreateInstance释放掉工场类并把接口的指针返还给客户端。
客户端此刻就可以操作这个接口挪用此部件中的要领了。
EJB技能架构中的工场要领模式
升阳(Sun Microsystem)建议的EJB(Enterprise Java Beans)技能架构是一套为Java语言设计的, 用来开拓企业局限应用措施的组件模子。我们来举例看一看EJB架构是奈何操作工场要领模式的。请考查下面的序列勾当图。
图7. 在升阳所倡导的EJB技能架构中, 工场要领模式也起着要害的浸染
在上面的图中,用户端创建一个新的 Context 工具,以便操作 JNDI 伺服器寻找 EJBObject。在获得这个 Context 工具后,就可以利用 JNDI 名, 好比"Employee", 来拿到 EJB 类 Employee 的 Home 接口。利用 Employee 的 Home 接口,客户端可以创建 EJB 工具,好比 EJB 类 Employee 的实例 emp, 然后挪用 Employee 的各个要领。
// 取到 JNDI naming context
Context ctx = new InitialContext ();
// 操作ctx 索取 EJB Home 接口
EmployeeHome home = (EmployeeHome)ctx.lookup("Employee");
// 操作Home 接口创建一个 Session Bean 工具
// 这里利用的是尺度的工场要领模式
Employee emp = home.create (1001, "John", "Smith");
// 挪用要领
emp.setTel ("212-657-7879");
代码清单7. EJB架构中,Home接口提供工场要领以便用户端可以动态地创建EJB类Employee的实例。
JMS技能架构中的工场要领模式
JMS界说了一套尺度的API,让Java语言措施能通过支持JMS尺度的MOM(Message Oriented Middleware 面向动静的中间伺服器)来创建和互换动静(message)。我们来举例看一看JMS(Java Messaging Service)技能架构是奈何利用工场要领模式的。
图8. 在JMS技能架构中, 工场要领模式无处不在
在上面的序列图中,用户端创建一个新的 Context 工具,以便操作 JNDI 伺服器寻找 Topic 和 ConnectionFactory 工具。在获得这个 ConnectionFactory 工具后, 就可以操作 Connection 创建 Session 的实例。有了 Session 的实例后,就可以操作 Session 创建 TopicPublisher的实例,并操作Session创建动静实例。
Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
prop.put(Context.PROVIDER_URL, "file:C:\temp");
// 取到 JNDI context
Context ctx = new InitialContext(prop);
// 操作ctx 索取工场类的实例
Topic topic = (Topic) ctx.lookup("myTopic");
TopicConnectionFactory tcf = (TopicConnectionFactory) ctx.lookup("myTCF");
// 操作工场类创建Connection,这是典范的工场模式
TopicConnection tCon = tcf.createTopicConnectoin();
// 操作Connection创建Session的实例,又是工场模式
TopicSession tSess = tCon.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
// 操作Session创建Producer的实例,又是工场模式
TopicPublisher publisher = tSess.createPublisher(topic);
// 操作Session创建动静实例,又是工场模式
TextMesage msg = tSess.createTextMessage("Hello from Jeff");
//发送动静
publisher.publish(msg);
代码清单8. JMS架构中,工场模式被用于创建 Connection, Session, Producer 的实例。
问答题
第1题、在这一节和上一节的类图中,我留意到Apple类的类图与Strawberry类的类图有一点点差异。在Apple类的类图左上角有一个夹子样的标识。请问这个标识代表什么意思。
第2题、在这一节的类图4中,我留意到 ConcreteProduct 类只呈现一次,但实现 Product 接口的类实际上可以有许多。这是否可以用在联接 Product 和 ConcreteProduct 之间的线旁注上 1,2,… 暗示呢? 记得我在UML图中曾见过这种暗号。
第3题、请问在本节的小花果园系统的源代码清单4里,Broccoli 类实现两个接口,VeggieIF 和 PlantIF。只有 PlantIF 才与工场模式有关。为什么不把 VeggieIF 接口归并到 PlantIF 接口中去?
第4题、请问在工场要领模式中,产物(Product) 何时应是抽象类,何时应是接口?
第5题、请问在工场要领 (factory())中,为什么要利用 if 语句作进程性判定来抉择创建哪一个产物类,而不利用多形性原则 (Polymorphsm) 来创建产物类?
问答题谜底
第1题、Apple类有性质(property),而Strawberry类没有性质。
#p#分页标题#e#
一个类的成员变量叫做属性(attribute)。性质与属性的区别在于性质是带着一套取值丶赋值要领的属性。一个类有了属性,其类图左上角就会有一只夹子。有些人认为,一个Java类有了属性才气被称做Java豆(Java Bean)。这只夹子就暗示这个类是一只豆。
一个企业Java豆,或 EJB (Enterprise JavaBean) 的类图左上角也会有一只夹子,夹子上面有一个E字以示与普通的Java豆的差异(请见下图)。
第2题、不能。在图4中联接 Product 和 ConcreteProduct 之间的线有两条,一条暗示两者之间的推广干系 (即有向上箭头的),另一条暗示两者之间的关联干系(即有向下箭头的)。在推广干系线旁写数字没有意义。在关联干系线旁写数字是有意义的,类旁的数字可以表白类的实例的数目。
本来的问题是关于类的数目而不是类的实例的数目,因此是错的。
没有任何必要用数字标明这一点,并且UML也不提供这种标志。
第3题、在面向工具的编程,出格是Java语言的编程中,接口经常用来符号一种身份(identity)。 VeggieIF 和 PlantIF 接口代表两种差异的身份。VeggieIF 表白 Broccoli 类属于蔬菜类品级, PlantIF 接口表白 Broccoli 类属于工场的产物类。
因此,固然把两个接口归并起来大概在成果上是行得通的,在原则上是不该勉励这样做的。
第4题、在工场要领模式中,产物(Product)可以永远是抽象类。但在一些景象下可仪简化为接口。
假如所实产物类( Concrete Product) 之间有配合的逻辑,这部门公有的代码就该当转移到产物 (Product) 中去,这样产物就必需是抽象类而不行能是接口。
反过来,假如所实产物类( Concrete Product) 之间没有任何配合的逻辑,那么产物(Product)就没有任何逻辑代码,它就该当被作为接口,而不是抽象类。但这不是必需的,仅是发起罢了。
第5题、多形性原则 (Polymorphism) 是在工具被创建之后才存在的,因此不能利用多形性来创建工具。factory() 要领一定长短常进程性 (procedural)的。