了解模式需要研究客户端程序(使用模式者)和模式的内部结构并且理解两者之间的通信接口模式本身因功能增加造成的变动(易碎性)以及客户端程序利用新功能的难易程度本文针对这些问题提出了一个具体的抽象工厂模式实现方案
模式结构介绍
这个模式由一个工厂类层次和N个产品类层次组成从每一个产品类层次中取出一个产品类形成产品类族这个类族的实例为产品族产品族中的产品之间有一种依赖关系一个具体的工厂类负责创建产品族中的各个产品
从图可以看出通信接口由一个抽象工厂接口和两个抽象产品组成模式部分显示了两个产品类 ProductA 和ProductB两个产品类族ProductAProductB和ProductAProductB以及两个产品类族对应的两个工厂ConcreteFactoryConcreteFactory
图 标准抽象工厂模式
从模式定义中知道这个模式的意图内容为
提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类工厂类层次的通信接口只有抽象工厂和创建产品族的各个工厂方法这些工厂方法不带任何参数并且返回具有抽象产品类型的具体产品实例这些使得客户端可以不依赖具体产品的类从而体现了模式的意图意图中的而无需指定它们具体的类可以理解为客户端在使用和创建具体产品时不给出具体产品的任何暗示
变动
我们分析模式的变动时必须先固化模式和客户端间的通信接口即通信接口是固定不变的分析变动就是分析模式的易碎性(模式实现适应变动的能力)对抽象工厂模式可以考虑两个变动
加一个产品类族
加一个产品类
变动一的易碎性较小我们只需在每个产品类层次中增加这个产品族中产品类作为抽象产品类的子类同时增加一个工厂子类来创建这个产品类族的产品族足以
变动二要求在模式中增加一个产品类意味着增加一个产品类层次由于通信接口中的工厂方法固定了客户能创建的产品的类型数目所以增加一个产品类意味着修改与客户端的通信接口进一步意味着旧的客户端要使用新的模式实现就要重新编码整个工厂类层次都需要改动综合上述这个变动的易碎性很大
设计
程序架构
一个程序可以从三个维度进行架构这三个维度是层级和服务层代表了不同的抽象程度比如操作系统的分层级代表不同的角色和职责比如Client/Server的两级模型服务代表了具有通用功能的支持模块
实现一个模式从级这个维度来讲可以简单地分成两级即客户端和模式端从服务维度讲模式可以利用某些服务如事务名字和目录服务安全等服务器提供商开发的设施服务模式本身也可以实现成供客户端访问的服务从而形成应用服务典型的有EJB组件应用程序的开发从层上考虑的比较少图体现了实现抽象工厂模式的程序架构
图 程序架构
JNDI
JNDI是Java Naming and Directory Interface的简写即Java命名和目录服务接口这个接口是名字和目录服务通用编程的API
命名服务是一个系统基础设施给对象绑定一个名字并能通过名字找到对象的机制这个名字一般是面向使用者的类似的服务实现有因特网域名系统(实现通过域名www 找到IP地址的手段)文件系统(实现通过文件名找到文件的手段)等
目录服务是名字服务的扩展除了提供名字绑定之外还允许对象拥有属性目录服务中的对象为目录对象图和图分别表示了名字服务和目录服务的概念定义
图 名字服务的概念
图 目录服务的概念
一个名字系统由相互联系的一系列上下文组成上下文是名字与对象的绑定集合相互联系的一组上下文能形成一个层次结构一个名字系统中所有的名字组成了这个系统的命名空间而且命名空间有它自己的命名规范一个目录库由一系列目录对象组成每个目录对象可以有若干个属性相连
JNDI架构如图其中JNDI SPI为服务提供者接口LDAPDNSNIS等为服务提供者JNDI API为客户程序使用服务的编程接口
图 JNDI架构
JNDI Java包JDK和后续版本已经包含了JNDI另外还有几个服务提供者如LDAPCOS和RMI其他的服务提供者可以从下载JNDI分为五个包javaxnaming javaxnamingdirectory javaxnamingevent javaxnamingldap 和javaxnamingspi 一般情况下只需要前三个包名字和目录操作都是针对一个上下文来说的但是没有绝对根的上下文所以就用一个初始上下文 InitialContext作为名字和目录操作的起点一旦有了这个上下文就可以用它来查找其它上下文和对象下面是进行名字操作和目录操作初始上下文类的关系
使用JNDI在模式的实现中我们可以用名字服务来实现子类的配置也就是说在名字服务中指定子类的名字由模式读取这个子类配置并创建一个子类实例返回给客户端客户端用抽象父类来返回子类实例通过这种机制实现针对接口编程而不是实现的重用的面向对象设计的原则
Singlton模式的实现可以利用JNDI名字服务来实现在名字服务中存放Singlton模式中的惟一实例使用者要使用这个实例可以使用JNDI编程接口查询这个对象我们的工厂类对象的产生就可以采用这个方法
类图设计
目前没有较好的办法解决增加产品类带来的旧客户程序不能透明地使用新产品类的方案如果能忍受这一点这个变动的其它影响还是可以解决的图为一个基于Class第一类对象的Java抽象工厂的模式变体结构图由此可以看出抽象工厂不仅是接口还是具体完成创建工作的类它只有一个工厂方法以抽象产品的类名为参数以Java类库的最顶层类Object为返回值客户端把通信接口抽象产品类名传给这个工厂方法接着通过一个强制类型转换而得到抽象产品对象命名服务实现产品类族中每个产品类名到具体产品类名的映射通过这样一系列的映射定义了每一个具体工厂类要创建的产品类族
图 抽象工厂模式变体类结构图
图中还显示了编写单元测试用例的类UnitTest和实用类SerObj
实现工厂通信接口的设计
.客户端代码如下
Factory factory=Factory getInstance
( ldap://localhost:/ dc = pattern dc = com ) ;
AbstractProductA productA=factory CreateProduct
( AbstractProductA的完全类名 );
名字服务包含Factory的名字到工厂类对象的绑定整个机制如图
图 工厂类的Singleton设计
.单元测试代码如下
.实现工厂Singlton模式的代码如下:
.具体的工厂方法代码如下
public class Factory implements javaioSerializable {
/**
接受抽象产品类的完全类名查询目录服务得到具体产品类的完全类名采用JAVA的CLASS为第一类对象的机制创建相应类的对象
*/
public Object createProduct(String vstrClassName ){
Context ctx = null;
String strEntryName;
String strEntryClassName;
Object oResult = null;
try{
ctx = getInitialContext ( ) ;
strEntryName = cn= + vstrClassName;
strEntryClassName = ( String )ctxlookup ( strEntryName) ;
try {
Class clTem;
clTem = ClassforName ( strEntryClassName ) ;
oResult = clTemnewInstance ( ) ;
}
catch ( Exception ex ) {
loggererror ( extoString());
}
}
catch ( NamingException e ) { }
finally{
try{
ctxclose ( ) ;
}
catch ( Exception e ) { }
}
return oResult;
}
}
实现产品通信接口的设计
抽象工厂模式的本意要求我们创建具体产品对象时客户端不能暗示任何具体产品对象类型的信息但是通过分析模式的通信接口可知客户端可以告诉工厂类这些具体产品的父类关于模式提到创建一些互相依赖的对象的本意我们可以在目录服务的目录库中实现图图分别定义了这样的产品族关系
图 目录服务产品族的配置
图 目录服务产品族的配置
.图表示在工厂类的工厂方法要求创建产品族时可以在目录服务中指定抽象产品的完全类名绑定到产品族的相应类名上
()单元测试代码如下
public class UnitTest extends TestCase{
public void testProduct () {