Java中对AbstractFactory和Prototype模式的替代方法

object_cat 2003-01-17 11:50:27
GOF的AbstractFactory模式提供了将使用实例与创建实例分离开的方法。以书中实现不同的look & feel的用户界面的代码为例,MotifyWidgetFactory的createWindow创建MotifWindow的实例,PMWidgetFactory创建PMWindow的实例,MotifWindow和PMWindow都是Window的派生类。

然而,之所以要使用上述方法,仅仅是因为创建实例时选取的class不同。如果将class名称看作参数,我们完全可以使用同一个Factory代替众多的XXFactory:

WindowFactory.createWindow( String implementation_name )

如,MotifWindow win = factory.createWindow("Motif")

进一步考虑,之所以有众多createXX方法,也仅仅是因为需要创建不同的实例的class不同。因此我们可以进一步用下面的方法代替大部分createXX:

WidgetFactory.createWidget( String class_name, String implementation_name)

如,
MotifyWindow win = (MotifyWindow)factory.createWidget( "Window", "Motif")
PMScrollBar b = (PMScrollBar)factory.createWidget("ScrollBar", "PM")

C++没有标准的运行时期根据类名创建编译时期不存在的class实例的方法,因此有AbstractFactory这样的模式出现。但是java的reflection机制使得我们很容易实现上面的方法。如:

Widget createWidget(String class_name, String implementation_name){
//假设package_name是WidgetFactory的一个成员,用于保存需要创建的class的package名称
try{
return (Widget)Class.forName(package_name + '.' + implementation_name + class_name).newInstance();
} catch(Throwable t){
return null;
}
}

这样做的优点是,不需要为每一种look&feel的实现都派生一个Factory的子类。缺点是,通过reflect创建实例比直接new创建的性能要差。考虑到JDK1.4已经将这种性能差距减小到可以接受的程度,使用reflection代替AbstractFactory在大部分情况下还是可行的。

只要简单地约定同一look&feel实现中,子类的名称都用look&feel的名称+抽象父类的名称就可以了。


Prototype模式同样是解决运行时期创建一个编译时期不存在的类的实例的问题,不过是通过调用预先注册的这个类的一个实例的clone方法,返回这个实例的一份复制来实现。同样,通过java的reflection机制,大部分情况下我们也不需要应用这种模式,除非会带来很大的性能问题。


实际上C++同样可以实现这样的功能。例如COM就能够通过指定class id来创建某个class的实例,只不过在java中这属于内建的功能,使用起来更加方便一些。
...全文
176 24 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
DavidBone 2003-04-20
  • 打赏
  • 举报
回复
up
biti_9512207 2003-02-11
  • 打赏
  • 举报
回复
rui
jeffyan77 2003-02-11
  • 打赏
  • 举报
回复
都去过节去了,呵呵
richardluopeng 2003-02-08
  • 打赏
  • 举报
回复
没有人说了,呵呵!
richardluopeng 2003-01-25
  • 打赏
  • 举报
回复
我也凑凑热闹,呵呵

我这个人比较笨,脑空间也有限,不知道怎么象大虾那样绕来绕去,呵呵,我喜欢三言两语把问题搞定,:)

关于楼主的这个问题,我提出一个问题,我觉得就没有办法解决,呵呵

就是假如有3个工厂:工厂1,工厂2,工厂3
有3样产品:产品1,产品2,产品3
我现在非要找茬,工厂1生产产品1,工厂2什么也不生产,产品2和产品3都是由工厂3来生产的,呵呵,没有脾气吧,呵呵

楼主,我相信你的方法不行了吧??!!

实际上工厂模式 client 只是需要知道我想要的产品谁能够生产,而不一定非要知道每一个工厂能生产什么,象我刚才举的例子里面,需要产品1,我就找工厂1,需要产品2,我就找工厂3,ok,没有问题!

结论:1,楼主的reflection有一定的道理,在特定的情况下确实可以少写代码,呵呵
2,替代AbstractFactory和Prototype模式的替代
没有必要,呵呵!


请多多指教!!
dickmi 2003-01-23
  • 打赏
  • 举报
回复
reflection是最彻底的实现代码无关,而其他的模式虽然方法各异,效果各异但是始终逃脱不了和代码相关的本质,所以两位讨论的虽然惹火朝天,其实我认为只是表述不同,意思是一样的,只不过每人有每人的偏好而已,我个人也很喜欢reflection,但是我不太会用它,原因是效率低,低的不是一点点,如果要耦合低,什么地方都用它不就降到最低,所有的调用全写配置文件就可以勒,但是这样会让这个应用无法运行。
说到底,还是一句话,看具体情况而定。
ajoo 2003-01-23
  • 打赏
  • 举报
回复
“当你需要创建新实例的时候”
你为什么就认为一定需要创建新实例而且必须要从缺省构造函数创建呢?这是你的需求决定的吗?还是你的假设?

如果你是在扮演工厂实现者的角色,那么就不是替代工厂,而是实现工厂。你可以用reflection实现工厂啊。象我给的那个WidgetFactoryRefl 的例子那样。


嗨。不过,还是算了,也许是我不明白你的意思吧。
何必争得面红耳赤呢?你觉得可以替代,那就替代嘛。

程序不象饭店的大师傅做菜,好吃就是好吃,不好吃就是不好吃。
程序的结构好一点,差一点,也许一百年也不会有人注意到。
要不厨师算是杰出人才,程序员只是打工的呢。
ajoo 2003-01-22
  • 打赏
  • 举报
回复
我有什么必要去耗费脑细胞找什么第三种方案呢?它是工厂实现者需要关心的。你写gui的,关心它干什么?

你的理论首先也不严密,什么不是返回引用?return new就不是返回引用?那你返回的是什么?

而且,你的反问偷换了概念。看看你原来说的是什么?
“方法无非就是两种:reflection,或者通过reflection获取一个AbstractFactory的实例”。
你的两种方法也只局限于reflection. 真没有其它方法了吗?

而你绕了一大圈,你的reflection是能对付pool呢?还是能对付new Adapter(obj)呢?

supersonics 2003-01-22
  • 打赏
  • 举报
回复
两位讨论的很激烈。
我斗胆插几句,不妥之处,还望见谅。
to ajoo(聪明的一猪):个人觉得,你确实是误解了object_cat(gcc)的想法。
请静下心来好好看看object_cat的发言。
to object_cat:
就你说的和你讨论一下:
我觉得是否采用你的方案要考虑的因素有:
1.已知控件个数,只是不知道有几套控件的情况(对应未知控件组),还是连控件的个数也不知道,或有可能增加目前未知的控件。
2.采用reflection时可以分别配置每一个控件,有灵活性,但是也割裂了控件之间的联系,比如换一套控件时要一个一个换,而不是只需要换掉一个factory.
个人觉得是否采用reflection的方式,取决于以上因素和别的因素。

object_cat 2003-01-22
  • 打赏
  • 举报
回复

我以为你质疑的“两种方法”是:“获取一个对象的实例无非是两种办法:new(无论是编译时期还是运行时期), 或者获取一个已存在的实例的引用。”return new和返回引用的区别就是有没有进行新实例创建的动作,虽然它们最终都返回了引用。这一点我想我们都明白,没必要在概念上纠缠,否则就变成没有意义的文字游戏了,累。

总之,我一直在强调的就是当你需要创建新实例的时候,使用reflection可能比间接地通过另一个class的方法要好一些,这与使用其他模式是不矛盾的。(就像我前面的代码)。你说的都是在factory method里面如何可以灵活地选择获得对象实例的方式,这没错,但和我说的不是一回事。你的所有例子都是在说是可以有哪些方法返回实例,而我想讨论的是当你决定了需要返回一个新创建的未知类实例的时候,如何去做更加灵活、方便。如果争来争去我们没有在说同一个问题而只是互相找对方的逻辑漏洞那就没什么意思了。


<q>我有什么必要去耗费脑细胞找什么第三种方案呢?它是工厂实现者需要关心的。你写gui的,关心它干什么?</q>

当我在讨论Abstract factory模式的问题的时候,我难道不是在扮演者工厂实现者的角色吗?我的重点难道不就是如何创建对象的具体实现方案吗?
ajoo 2003-01-21
  • 打赏
  • 举报
回复
不错,你是可能碰到需要new instance的情况,但也可能碰到需要从singleton或者pool中返回的情况,也可能碰到需要new Adpater(someobj);的情况,还可能碰到很多很多你不可能预料到的情况。
abstract factory并没有对这些情况做出任何假设,根据需要,你总可以选取最合适的实现。你的reflection不过是这些可能的实现的一种而已。

"方法无非就是两种", 这是你的假设,设计时最忌的就是自己随便对需求做假设。你怎么知道只有两种呢?有几种又和你的gui有什么关系呢?

如果使用工厂接口,你唯一向客户要求的就是:"给我一个instance". 为什么不必要地要求更多呢?

“通过简单的约定免去写Abstract factory的必要”。
这样一个简单的接口至于想方设法地去避免吗?为什么要避免?你的reflection的方法就比abstract factory简单?我看未必。对一些简单的anonymous class可以解决的场合,你这个reflection就是over kill.


object_cat 2003-01-21
  • 打赏
  • 举报
回复

singleton -〉返回引用
pool -〉返回引用
new Adpater(someobj) -〉创建实例
……
对于对象创建,除了创建实例和返回引用之外你还能找到第三种方法吗?
jeffyan77 2003-01-20
  • 打赏
  • 举报
回复
楼主说:“但如果我在说鸡,你在说鸭,那我就不知道该说什么了……”

呵呵,我觉得我说的就是你最初的帖子的关键问题,你的帖子说得很明确,是要给出多操作系统中控件LaF中的abstract factory模式的.替.代.方.案。

我看不出为什么需要你的这个替代方案,在我看来,软件系统的.需.求以及需.求.的.可.变.性.(在这里就是出现新的视窗操作环境的可能性)指向abstract factory+bridge设计模式方案。

在Java里面使用refection不是明智的做法,我很难想象每一种控件的创建都涉及到使用reflection,我同意ajoo的意见。

我建议您在考虑一下bridge模式在Java的LaF中使用的情况,相信那个时候您可能会更容易理解我的意见。

呵呵,大家切磋
object_cat 2003-01-20
  • 打赏
  • 举报
回复

获取一个对象的实例无非是两种办法:new(无论是编译时期还是运行时期), 或者获取一个已存在的实例的引用。所有的Creational Patterns的实现最终都要落到这两者之一。如果我们不返回引用,我们就要new。不管需求是什么,我们总会碰倒需要返回new instance而不是reference的场合,对吗?

对于为什么需要从一个公有类的共有构造函数创建对象,首先,这是你直接创建编译时期不存在的类实例的最直接方法。试想如果你的java GUI程序支持动态安装别人写的界面控件(由用户选择jar文件,并立即切换成这种GUI界面实现),方法无非就是两种:reflection,或者通过reflection获取一个AbstractFactory的实例。最终你还是要碰到动态类型转换。如果你可以通过简单的约定免去写Abstract factory的必要,使用reflection创建对象又有什么不好呢?

其次,资源的分配和实例的初始化本质上说是没有太大相关性的两件事,因此我们大可以把constructor中做与参数有关的初始化的工作拿出来,这样可以使得代码的内聚性更高。这也符合让一个方法完成使其有意义的最小功能集合的原则。你可以 Array a = new Array(100), 也可以Array a= new Array(); a.setSize(100)。把初始化的工作延迟到constructor之后,至少提供了更加灵活地使用实例的可能。

编译器只能解决类型安全,无法解决语义安全。不是说类型安全不好,而是你确实会碰到只能依靠语义和约定的场合,这时编译器是无能为力的。是否要牺牲类型安全来换取灵活性(如提供界面控件的“即插即用”功能),当然要看具体需求而定。
ajoo 2003-01-20
  • 打赏
  • 举报
回复
你的需求真的需要工厂的实现每次创建一个新的实例给你吗?
为什么?
你的需求真的需要工厂的实现要从一个公有的类的公有的缺省构造函数中创建对象?为什么?
不必先拷贝书中的话,需求永远是第一位的,我们又不是为了模式定义而编程。
你能先完整描述一下需求吗?

至于类型安全,明明是编译器就可以简单地找到错误并帮你预防的,为什么要去研究心理学呢?多划不来呀!
object_cat 2003-01-20
  • 打赏
  • 举报
回复
to ajoo(聪明的一猪):

<q>
首先,让我们分析一下abstract factory对用户的承诺:
interface XFactory{
X getInstance();
}
那么,这里,都有些什么承诺呢?
getInstance()将返回一个X的对象。

没有了,再没有其它的了。
</q>

你说的是Abstract factory吗?引用GOF原文:

AbstractFactory usually defines a different operation for each kind of product it can produce. The kinds of products are encoded in the operation signatures. Adding a new kind of product requires changing the AbstractFactory interface and all the classes that depend on it.

也许我们在讨论之前需要统一术语的定义。很明显,通常说的Abstract factory需要创建一*族*对象实例,如果仅仅是你说的一个getInstance,那我更倾向于称呼它factory method,那就不是我们要讨论的问题了。

<q>
你这个实现不能使用decorator, adapter, 不能使用静态工厂,不能使用其它的抽象工厂,不能使用singleton, 不能使用任何其他的非缺省的构造函数。
</q>

还是那句话,我们讨论的是***需要创建实例***的场合。也就是Abstract factory模式声称要解决的问题。没有这个问题就没必要讨论了。我们完全可以实现下面的代码:

String cls_name = ... ;

if( 需要使用singlton )
return singlton;
else if( 需要创建实例){
try{
// 创建实例,这里才是问题的所在
Widget w = (Widget)Class.forName(cls_name).newInstance();

// 获取与该class关联的decorator列表并完成初始化工作
List decorator_chain = SomeClass.getDecoratorChain( w.getClass());
for( Iterator it = decorator_chain.iterator(); it.hasNext(); ;){
WidgetDecorator wd = (WidgetDecorator)it.next();
wd.initWidget( w);
}
return w;

} catch(...){
}
}

我可以用一个统一的factory method完成实例的创建工作,在其中使用reflection代替abstract factory。我可以根据需要返回singleton,使用adaptor,使用对象池,等等等等。但是很明显,只有创建实例的这部分代码是我们讨论的对象,其他部分到底如何设计与我们这个话题无关。这里要解决的问题就是***如果需要创建实例***,如何来创建。至于实例创建完了以后如何应用各种模式去初始化,或者之前决定到底需不需要创建实例,由谁来创建实例,这是另外的问题。完全正确但与讨论无关的结论,基本上没有什么意义。


<q>
如果refactor的时候我改变了类的实现(如改使用静态工厂,返回一个内部类的对象等等),是不是要重新测试这个reflection的模块?
</q>

这种问题我可以归结为:如果预先约定了某个interface的semantics,但是其他人的实现,或后来修改过的实现,违反了这个semantics怎么办?呵呵,这个我想单纯靠模式是解决不了的。编译器能解决吗?也不能。如果你约定了某Abstract factory的某个方法应该返回一个线程安全的实例,但别人的实现偏偏就是返回了一个不安全实例呢?你怎么保证这个分支永远能被测试到?也许解决这样的问题需要依靠心理学或其他什么,但那也不是我们这个帖子讨论的问题的范畴了。
ajoo 2003-01-20
  • 打赏
  • 举报
回复
其实,把这个方法作为abstract factory的一个实现,比要取代它这个提法要合适得多了。
final class WidgetFactoryRefl implements WidgetFactory{
public Widget getWidget(){
return (Widget)type.newInstance();
}
private final Class type;
private WidgetFactoryRefl(Class t){this.type=t;}
static WidgetFactory instance(Class t){
if(!Widget.class.isAssignableFrom(t)){
throw new ClassCastException();
}
//等等等等其它的对这个类的检查。
return new WidgetFactoryRefl(t);
}
static WidgetFactory instance(String classname){
return instance(class.forName(classname));
}
}

为什么要取代别的方法呢?大家各有各得应用场合,由具体需求和用户来决定使用哪个实现不是更好?
ajoo 2003-01-20
  • 打赏
  • 举报
回复
首先,让我们分析一下abstract factory对用户的承诺:
interface XFactory{
X getInstance();
}
那么,这里,都有些什么承诺呢?
getInstance()将返回一个X的对象。

没有了,再没有其它的了。
正因为承诺很少,它才能在实现时用return singleton, return new XImpl(), return new XImpl("hello"), return XImpl.instance(), return XAdapter.adapt(YImpl.instance()), return this;等等等等。

再让我们看看你的reflection的newInstance(classname):
1.必须新生成一个对象。
2。classname必须是一个可实例化的类
3。classname代表的类必须有公有的缺省构造函数。
4。classname必须是实现了X接口的类。

对以上四点,编译器都不能检查,你要在运行时检查。你怎么保证这个分支永远能被测试到?为什么要无缘无故给测试找麻烦?能在编译时检查的问题为什么留到运行时?
即使你的拼写没有错,如果refactor的时候我改变了类的实现(如改使用静态工厂,返回一个内部类的对象等等),是不是要重新测试这个reflection的模块?本来用工具可以自动refactor的,或者至少编译器可以马上把所有需要改动的代码报告出来的,现在全得祈祷测试能够马上找到这些不一致,或者,程序员记忆力极好,没有忘记这些reflection.
这不是给refactor制造障碍?

应该尽量避免downcast,这是一个常识,你有什么理由justify这个缺点?类型安全真是可有可无的吗?

你这个实现不能使用decorator, adapter, 不能使用静态工厂,不能使用其它的抽象工厂,不能使用singleton, 不能使用任何其他的非缺省的构造函数。
你叫它灵活?

当然,不能把reflection一棒子打死。你的这个方法,和jdk的dynamic proxy一样,都有它方便的优点,不必对每一个不同的Widget类写一个工厂实现。

但是,方便也是有限,我用anonymous class做一个工厂也不费什么力气:
new WidgetFactory(){public Widget getWidget(){return new WidgetX();}};
也不过是一两行的代码而已。

总而言之,剥离了实际需求来谈实现是不会有结果的。reflection的实现也肯定是有它的用武之地的。不过,我要说,它应该是最后要考虑的方法。
object_cat 2003-01-19
  • 打赏
  • 举报
回复
to ajoo(聪明的一猪) :

你说reflection效率低,这个没错,但正如我前面所说,在现代的JVM中reflection调用和普通方法调用已经不存在数量级的差别。你说不灵活,这点我不敢苟同。不论是FactoryMethod还是AbstractFactory,他们做的事情不外乎两点:1、创建一个类的实例, 2、根据传递的参数初始化这个实例。对于前者,由于C++没有标准的创建未知类的实例的方法,才不得不通过约定创建实例的接口,由具体的Factory类来完成。而java可以通过reflection动态创建实例,因此很大程度上就没有必要专门为了创建实例而写代码。对于后者,既然初始化工作可以放在某个公有方法里面完成,且已经约定初始化方法的接口,为什么不能让要创建的类来实现这个方法呢?如:

try{
String cls_name = ... ;
Widget w = (Widget)Class.forName(cls_name).newInstance();
w.initialize(...);
} catch(Throwable t){
return null;
}
}

即使由于某种原因初始化的工作要放在另一个类里面进行,也完全可以用下面的方式实现:

try{
String cls_name = ... ;
Widget w = (Widget)Class.forName(cls_name).newInstance();
WidgetInitializer wi = WidgetInitializerFactory.getInstance(w.getClass());
wi.initialize(w);
} catch(Throwable t){
return null;
}
}

你说“在新的jdk api里,已经有用静态getInstance()方法来取代公有构造函数的趋向”,我认为JDK里面出现了一些Factory method只能说是“对特定的需求采用最合适的实现”,我不认为由此就可以推论factory method就有取代constructor的趋势。毕竟简单地约定提供缺省构造函数并实现某些统一的初始化接口,和写N个XXFactory和createXXX方法,谁更方便呢?况且AbstractFactory还有一个问题:它限定了你能创建的class的范围。如果今后有新的UI control出现,它是Widget,但既不是Window也不是ScrollBar也不是任何createXXX方法返回的class,那么是不是还要修改所有的AbstractFactory派生类以加上这个新的方法呢?所以,我认为恰恰是AbstractFactory不灵活,只有在不得已的情况下才需要考虑这种实现。

你说的singleton例子,它很正确,但与我们讨论的问题无关。任何一个模式都有自己的应用场合与范围,AbstractFactory或Prototype试图解决的问题无非是通过统一的方式创建未知类的实例,如果超出了这个范围,不论它是一个多么完美的singleton或其他pattern,那跟我们的话题又有什么关系呢?

至于类名的拼写问题,我也不认为这是一个问题。与灵活性、可维护性比起来,类型安全真的那么重要吗?如果类名写错了,不外乎抛出一个ClassNotFoundException,根据Stack trace找到出错的代码所在,改正它就行了。套用一句广告语,“没什么大不了的”。况且,即使是使用AbstractFactory也不能保证代码书写错误造成的bug 100%被编译器发现,其实说到底这个问题与任何模式都无关,我们可以用另外一个“高效测试与debug之我见”之类的贴子来讨论它。

to jeffyan77(jeffyan77) :

可能你没有弄清楚我们在说什么。你所谓的工厂继承结构的消失,与扁平化或者polymophism无关,是因为考虑了另一种完全不需要工厂类的实现方式。“皮之不存,毛将焉附”?你可以提出这种方法的种种缺点,但如果我在说鸡,你在说鸭,那我就不知道该说什么了……

jeffyan77 2003-01-19
  • 打赏
  • 举报
回复
将一个类的继承结构扁平化,就是我指的polymorphism退化。一个工厂的等级结构可以扁平化,并最终退化成为只有一个具体工厂类。polymorphism退化不能说成是不能polymorphic。

您的做法无关对错,把一个工厂类的继承结构扁平化,并不是一定就不好。正如我强调的那样,好与不好,一定要看软件系统的需求以及需求的变化,选择怎样做需要考察系统的可维护性、可复用性是得到了增强,还是被减弱。
加载更多回复(4)

51,398

社区成员

发帖
与我相关
我的任务
社区描述
Java相关技术讨论
javaspring bootspring cloud 技术论坛(原bbs)
社区管理员
  • Java相关社区
  • 小虚竹
  • 谙忆
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧