不过,banq,你比较的好像是ioc injection和工厂啊。
我从来都是倾向于使用ioc,通过constructor来初始化final的接口变量。

但是,使用文中所说的ioc,并不能说明必须要用pico啊。
象这段代码:


public class client{
   public static void main( String[] args ) {
    DefaultPicoContainer container = new DefaultPicoContainer();
    container.registerComponentImplementation(CImp.class);
    container.registerComponentImplementation(B.class);
    B b = (B) container.getComponentInstance(B.class);
    b.someMethod();
   }
}

为什么不能直接这样?

B b = new B(new CImpl());

不是更简单明了?

我的疑问是pico对比于上面这种直接调用有什么优势呢?

这个区别实际是实例和类的区别,在程序中写入:
B b = new B(new CImpl());

这是实例生成,如果写成我那样,是一种动态类加载的用法,我这种明显的优点是,CImp.class是一段字符串,这段字符串可以从XML配置文件中获得,那么第三者或者框架容器的使用者,只要在XML配置文件中写入CImp.class字符串就可以,或者写其它如CImp2.class CImp3.class,是不是灵活性增强。

EJB为什么有ejb-jar.xml配置文件,也是出于同样的原因和目的,实现机制也差不多。

也就是说,上面的示例代码实际中会是类似如此:


B b = new B(Class.forName(implclassname).newInstance());

对么?这么说,自然是有道理,实际上,这个区别就是reflection和直接new这个方法的区别了。

不过,你说了reflection的好处,但是reflection也是有坏处的。

1。类型安全。reflection需要cast,这对程序的发展是个负担。因为修改某段代码或者某个类后,编译器不一定会给你报告可能的类型匹配错误。这就意味着,如果工程大了,很可能牵一发而动全身。

2。灵活性。面向接口编程提供了极大的灵活性。接口的实现类可能是一个public类,也可能是被隐藏在某个类或者package中;可能是一个单独的类实现的,或者是从一个adaptor, decorator类来的;实现类的构造函数可能是public的,也可能是通过某个工厂方法直接返回这个接口,甚至可能是一个singleton。
这些灵活性,在type.newInstance()方法中全部失去了。


其实,遇到这种可能需要通过配置文件来决定实现类的情况,我一般会使用abstract factory。

定义一个接口:


interface CFactory{
C getC();
}

然后,通过ioc,把这个CFactory对象注入。
于是,代码变成了:

B b = new B(cf.getC());

这个factory可以有普通的实现:

new CFactory(){
public C getC(){return new CImpl();}
}

如果喜欢配置文件,用reflection,也可以呀:

class ReflectionCFactory implements CFactory{
private final Class type;
public ReflectionCFactory(Class t){this.type=t;}
public C getC(){return (C)type.newInstance();}
}

用decorator, adaptor, bridge,dynamic proxy也都可以做到呀。


为什么非要强迫用reflection呢?

罗嗦了半天,其实我的主要观点不是说用factory肯定比reflection好。而是,为什么pico container要依赖于reflection呢?
而且,难道pico的功能就是提供一个用reflection来注册对象生成的功能?这,也太trivial了吧?任何人都可以自己写一个reflection工厂啊。

我只是使用reflection比喻pico,正好相反pico不是用reflection。但是达到了reflection效果,这就是它的特点啊。

pico container并不依赖于reflection,你可以看看它的代码。


如果使用了register(B.class)这种东西,不用看代码,也知道其内部肯定使用了reflection。
不用reflection怎么可能达到reflection的效果呢?那reflection不是没用了?

不过,不管这个了,banq, 能不能举个例子说明你使用pico的原因呢?我就是想能够很直观地看到为什么大家需要pico。它到底是干什么的。我想我现在还不是很关心它的实现,只要能看到它给用户提供的接口和用户如何用它就好了。

我上篇有误,Pico使用了Class.forName,但是它增加其它代码,用来克服直接使用Reflection的缺点,特别是Clast问题。

我是在扩充JdonSD框架时想到使用它,之前我其实我已经朴素地实现了它,这次碰到相同的问题:

问题:在框架中有两个Action,例如Action1 和Action2,他们其中都有数据库操作,我要把数据库操作延迟到具体系统中实现,他们数据库操作基本操作是同一个资源,因此起初有下列方案:

将框架中的Action1和Action2分别做成Abstract Class,将数据库操作做成abstract方法,这样在具体系统中,直接继承这两个Action就可以。
但是发现这两个继承子类都只有一个方法,方法中只有两行代码,而且都是再委托同一个EJB处理。

很显然,我想把这两个类的数据库操作方法合并,合并成一个类,这样代码简单,框架也易于使用。

方法:在框架中创建一个DataHandler接口,在Action1和Action2中的数据库操作都委托给DataHandler实现。那么在Action1的代码如下:
public class Action1{

private DataHandler dataHandler;
public Action1(DataHandler dataHandler){
this.dataHandler = dataHandler;
}

}

很显然,我想在具体系统中,框架使用者直接将dataHandler注射进入Action1即可。

因此我着手引入Pico container,因为Pico是最简单的这类注射实现,我不喜欢复杂的框架,因为我觉得还是太重,那些所谓轻量级别框架开发者埋怨EJB太重,其实他们自己做的东西哪样不重啊,看看Apache那些项目就知道。话说多了,总之Pico container最简单,所以选择它。

但是,正如我文章写到,我当时在工厂模式和Pico注射模式之间选择,发现了他们的特点,因为我的Action1和Action2属于Struts框架的子类,因此无法再引入Pico,这问题苦恼了我很长时间,最后结果是放弃了引入Ioc模式,不过将感受写了这篇文章。

假设:如果我的框架不是基于Struts等框架下再实现,那么就可以使用Pico,类似Spring那样提供一个app.xml配置文件,那么在系统启动时,将配置文件中的子类一个个注射到Action1和Action2中就可以了。

其实,正如你说,使用工厂模式或reflect也可以,一则,这两个技术实现需要很多细节编码,不方便,第二,自己实现可扩展性不强,因为在一个系统中,我不可能只有一个子类要注射,可能需要很多,PetStore1.3的WAF的是使用工厂模式,它的解析和子类启动过程非常复杂;

还有一种应用需求,我在计划,但没有实施,在每个类中我都要Log,每个类每次Log都要初始化,这些在每个类代码都要写,通过使用Pico或AOP,可以将这些Log提炼出来,一般建议是,写个抽象类就可以,但是具体子类要继承这个抽象类,注意Java子类只能extends,这样做实际使得你的子类丧失了在extends其它的权力,相当于枪毙了extends继承功能,想想多可怕,你的这些类就是final类,死类,何来扩展性?

johnson建议把配置从Java代码中分离出来,我想这是Spring和Pico的最大区别吧。
另一种便是IOC II && III的区别。

--如果用工厂模式,factory.createObject()则代码内部很难与接口打交道。

--很多时候使用单态加载,可能还需要额外的配置数据,可能出现配置管理不一致,另外,类中会有很多与业务无关的配置管理代码。

可以看看spring m4版中的jpetstore例子,除了配置文件-xml形式指定了实现类的位置,所有“管道”代码中都可以只使用接口类。

==>假设:如果我的框架不是基于Struts等框架下再实现,那么就可以使用Pico,类似Spring那样提供一个app.xml配置文件,那么在系统启动时,将配置文件中的子类一个个注射到Action1和Action2中就可以了。

spring强调的是各层的分离,你的程序可以有任意的组合,eg.
struts,webwork+spring+hiberate,ibatis,etc.

而且你完全可以只用spring实现所有的功能。

jpetstore中有这样的例子,不过不知道和banq大哥所说的是不是一个想法。
参照BaseAction类,此类扩展Action,然后从当前上下文中传到spring对应的应用程序上下文,从而获得业务层对象的依赖。
部分代码如下:
public abstract class BaseAction extends Action {

private PetStoreFacade petStore;

public void setServlet(ActionServlet actionServlet) {
super.setServlet(actionServlet);
ServletContext servletContext = actionServlet.getServletContext();
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
this.petStore = (PetStoreFacade) wac.getBean("petStore");
}

pico没有使用Class.forName
用的Constructor.newInstance

>>将框架中的Action1和Action2分别做成Abstract Class,将数据库操作做成abstract方法

我一直认为,除非是自己内部使用的私有(至少是package私有)类,尽量不要用extends。继承不够灵活,父子类耦合过大,而且你还占用了子类的唯一一个父类的位置。
从实际经验中发现,90%的情况,用接口组合都何以完成继承所能完成的功能,而且灵活很多。


>>因此我着手引入Pico container

遇到这种情况我的处理方法很有可能和你的一样。定义一个接口(就等于一个插槽了),然后让外界插入具体实现。
不过,我仍然不能理解为什么一旦想用ioc就要引入pico。在我看来,以及我用了这么长时间的ioc(虽然自己从来不知道它有这么个名字)的经验,不需要额外的库来支持这样一个简单的东西呀。

>>因为我的Action1和Action2属于Struts框架的子类,因此无法再引入Pico

首先,不是很理解为什么就不能用pico。难道使用pico需要继承吗?即使要求继承,难道不能用内部类之类的东西?
其次,这正说明了设计一个库,框架时,要求用户subclass是多么失策。每当看到这样的库,看到一些东西直接用类输出给用户使用(而不是接口。最近在refactor一个ibm ctg的项目,就发现ibm的JavaGateway作为一个类输出,造成了refactor的巨大不便。),我就很别扭。可是,偏偏这样的设计到处都是。


>>使用工厂模式或reflect也可以,一则,这两个技术实现需要很多细节编码,不方便,第二,自己实现可扩展性不强

相反,我反而觉得自己实现的扩展性更强。
就象我前面举的abstract factory的方法,完全可以实现class.forName, newInstance, container.lookup等等功能。但是反过来却不是这样。这是因为接口只描述需求,不隐含任何实现细节,自然是最灵活的。

至于技术细节,也并不麻烦。实际上,只是多出了一个接口。如果暂时只需要用reflection或者pico来创建对象,完全可以仅仅实现FactoryUsingReflection甚至FactoryUsingPico。用不到的可以搁置到以后再实现。
甚至,如果没时间编写factory实现,都可以先放个接口在那里,等着外面注射就是。


>>还有一种应用需求,我在计划,但没有实施,在每个类中我都要Log,每个类每次Log都要初始化

我很同意你对abstract class的意见。实际上我基本不建议使用abstract class。
至于log,往往也就是两种思路:
1。下沉法。(见最后的代码1)

定义Log接口,然后给每个需要log的类声明Log变量,让外界注射实现。(但是不一定就意味着用第三方的库如pico什么的)。这等于将Log的功能下沉到每一个business实现类。

2。上升法。(见最后的代码2)
如果各个实现类不关心log,而实现类的使用者关心log,那么,可以使用bridge或者decorator模式(或者aop的mixin啦),把实现的接口bridge到带有log功能的实现上去。这等于把log的功能上升到一个专门的小framework中。于是每个business实现类就不需要关心log了。

不知道你这个log的需求到底是什么样的。如果能够贴出来大家讨论,我想不仅是这个问题,也会让大家对ioc等东西的讨论更加有的放矢。


>>想想多可怕,你的这些类就是final类,死类,何来扩展性?

说说我对final类的想法。我认为一个设计良好的类应该就是final的。因为扩展都通过接口组合(或者叫ioc)来实现。
而final类避免了对子类耦合的担忧。不用担心任何一个方法被某个子类override了。
既然继承是要尽量避免的,那么final就应该是尽量提倡的。
我甚至比较烦恼Java把类的可继承属性设为了缺省的。我倒觉得final应该是缺省的。

对了,说到最后,我也还是对为什么需要pico不很明了。用ioc我理解,这也是我推崇的做法,但是为什么用ioc就要用pico?这两者有什么必然的联系吗?能否麻烦banq老大再不厌其烦地给讲解一下?具体例子最好。


对了,banq,这个坛子对代码的处理好像有点问题呀。我贴两段代码,第一段代码后面的子就越来越小了。搞得我只好把代码贴到最后。还是我哪里敲的不对?

代码1(下沉法):


final class MyBusiness1 implements Business{
private final Log log;
MyBusiness1(Log l){this.log=log;}
......
void f(){...l.log("v="+v);...}
}

代码2(上升法)


public final class LoggedBusiness implements Business{
private final Business b;
private final Log l;
public void f1(){l.log("f1()");b.f1();}
public void f2(){l.log(
"f2()");b.f2();}
private LoggedBusiness(Business b, Log l){
this.b = b;
this.l = l;
}
public static Business decorate(Business b, Log l){
return new LoggedBusiness(b, l);
}
}

其实你的概念中已经接受Ioc,至于是使用Pico还是Spring,或者自己实现,要看项目而言,我推荐Pico是因为它是最简单。

by way:我想使用Pico或AOP实现log目标是:在每个具体类中不再编写有关log的代码,你的两个代码中都有log处理,我是想完全消灭。

> 其实你的概念中已经接受Ioc,至于是使用Pico还是Spring,?> 者自己实现,要看项目而言,我推荐Pico是因为它是最简单。
>
>
> by
> way:我想使用Pico或AOP实现log目标是:在每个具体类中不再
> 嘈从泄log的代码,你的两个代码中都有log处理,我是想完?> 消灭。

是啊。我就是想知道为什么你推荐pico啊。从前面的例子来看,单纯从简单性考虑,应该是直接new最简单了。而我猜想pico肯定提供了超过用class name登记对象创建的东西,否则,似乎就没有必要找这个麻烦。可惜,到现在,我仍然不清楚这个pico存在的理由。
或许,pico就是一个可以用xml等配置文件来配置的对象创建框架?可是这仍然和container的这个称呼不大吻合。


对于log,就算你用aop,也要写一个aspect吧?那不也是log处理吗?用bridge模式,和aop一样啊,只需要在一个地方实现log逻辑。只要需要log逻辑,就必须在一个地方实现它,不可能彻底消灭的。(当然,用已经做好的支持log的框架,比如如果pico支持log的话,就可能不用自己写了)

有机会我们再充分交流一下吧,关于pico是否用,取决于目的。你大可以一直不用它,就象EJB,一直有人抵制,不用EJB也可以做系统。

我个人觉得Exo中的portlet container使用Picocontainer比较好,因为我在知晓Exo和Pico之前,就和透明说,想找使用AOP解决portlet container的方案,或者自己设计,我也在研究门户,这是我感觉,结果,透明告诉我Exo已经这样做了,我拿过来一看,果然是这样。

以上言论只代表我个人经验和想法,至于要写出1+1为什么等于2的理由,恐怕写本书都写不完,而且话语多了,误解也多,漏洞也多,也确实没那么多时间写那么多逻辑过程。

我主要致力于JdonSD框架完善和发展,因此,对可重用性和灵活性一直比较注重,我不想让我的框架太复杂,所谓大道之简,这样框架使用才方便、稳定性才更强,性能也好,可以说这是我一直追求的个人目标。

经常写错别字:上篇中应是“大道至简”。

banq, 我可是看着你的那篇文章有所疑问,也看你说欢迎讨论这些pico等框架,才来提问的啊。

可是,你只跟我说pico简单,用它取决于目的,这对我来说太难理解了。也并不能回答为什么要用它的问题呀。

我知道一般讲抽象的道理很难get-across。所以才希望你可以据你的丰富的实践经验给出一个具体例子来。然后我们就可以看到用了pico的好处,不用pico而用其它alternative的坏处。

只有这样,才能叫摆出了1+1吧?我问的不是为什么1+1=2,只要看见了这个1+1我想我还是能算出来结果的。但是没有1+1凭空怎么说1+1=2呢?

我也关注可重用性,可扩展性,但是从你前面给出的例子,至少它们表现出来的可重用性和可扩展性并不是那么理想的。如果不是有其它的限制条件或者需求的上下文,我认为有比它可重用性,可扩展性更好的方案。

总结一下你的观点:
1。用ioc的话,用pico会很简单。(我想这个“简单”也是一个比较之后的结论吧?跟谁比呢?跟直接new或者跟abstract factory比似乎明显pico更复杂啊)
2。用pico取决于目的。(什么目的呢?我想我一直问的就是这个问题)
3。...就象不用ejb也可以做系统。(这话是不是隐含一个前提:不用pico或者ejb没有用它们在可移植性啦,扩展性啦,重用啦等指标上面做得好?可是,我不是在试图反对pico,而正是在试图给自己找一个用pico的理由,也就是说,是哪个指标?用pico好在哪里?不用pico坏在哪里?pico是什么?)


总而言之,老兄的所有回答都对我来说太抽象太深奥了。我这个人笨,还是对具体的例子更敏感。不告诉我1+1,我还真就死活不明白什么是2。哪怕你嘴皮子磨破。^_^


看来这个thread要结束了。老兄是关注于实践的人,没时间在这里磨嘴皮子,也没义务给别人答疑解惑。这我理解。看来,要想理解pico,还得是自己动手啊。本来想发一下懒,吸取一下前人的经验和精华,看来天下没有免费的午餐啊。
不过,谢谢banq的耐心和时间。

自己看了一下pico的文档。
据我的理解,pico似乎主要就提供了一个动态创建对象的framework。所谓ioc,是pico存在的主要原因,因为没有ioc,就根本不需要组装对象,但是,并不是说用ioc就必须用pico。如果实现类的组装可以在静态决定,那么仍然是直接new最好。pico相比于直接new仍然过于复杂,而且也缺乏类型安全。

但是,有些时候,对象组装必须由部署配置决定,或者在运行时决定。这时,就需要一个机制来动态决定实现类。

我本来觉得,象new A(new B(), new C())这种类似的对象创建,即使换成动态的,也不过就是读一些配置文件,比如读到字符串"B"和"C",然后用Class.forName就行了。

但是,仔细一想,我前面对这种动态对象创建的trivial判断似乎太过草率。有些时候,这个动态创建的对象很可能是个相当大的树。比如:

可以
new A1(new B1(new C1(), new D1()), new D1());
也可以
new A2(new C2(new D2()));

都是一个构造A接口对象的方法。而动态创建这种比较深的树的动作并不简单。需要用reflection来找到这些实现类的构造方法的所有参数类型,然后递归构造各个参数component。


我想,pico存在的价值就主要在此吧。


当然,pico还提供了multicast和lifecycle。不过,我觉得这都是附加功能了。

不过pico似乎对一些东西还是无能为力,看下面的例子:
D d = new D();
new A1(new B1(d, new C1()), new B2(d, C2.instance()));

1,class C2隐藏了constructor,而通过一个工厂方法instance()生成对象(singleton应该是最常见的场合),pico如何处理?
(不过我想这应该是可以扩展的,只要加入一些特定的registerComponent函数就行了)

2,pico似乎对每个参数都是调用newInstance()来生成。
那么,当B1和B2的构造要共享一个相同的d对象时,pico表示得了吗?


3,pico给的例子似乎都是一个container里面,对应一个接口,只有一个实现。
但是,上面的例子中,C1和C2都实现了接口C,B1, B2都实现了接口B,那么pico构造的时候,通过参数的类型是无法知道选择哪个实现的。此时,是二义性吗?


banq和pico网站上的例子,都比较注重卖ioc的概念,但是对我这个一直在使用ioc的,这就似乎失去了重点。
对我来说,这个"why pico"的答案不是ioc,(因为那是理所当然的),而似乎是动态创建,动态组装,动态注射。关键在于一个"动态",这点前面banq也提到了。