浏览 20418 次
锁定老帖子 主题:pico印象
该帖已经被评为精华帖
作者 正文
   发表时间:2005-02-26  
一直都听人提起pico,说它是ioc框架如何如何,让我特别纳闷,一个简单的ioc,不就是“衣来伸手,饭来张口”吗?怎么会需要一个特殊的框架来支持呢?

看见一些人津津乐道于container.registerComponentImplementation(A.class);
这种冗长和费解的对象创建方式,也让我对pico没一点好气:什么嘛,这不是自己给自己找麻烦玩儿吗? 语法古怪,依赖关系不清,还牺牲了静态类型安全。

于是到处找高人请教:pico到底能做什么?

得到的答案不太让我满意。banq甚至不容分说,把怀疑这种奇怪的对象创建方式而仍然坚持new C(new A(), new B())这种老土方法的直接斥为:不懂面向对象,不考虑设计的灵活性。一棒子打晕了事。


这两天终于抽出点时间,自己动手,丰衣足食,看起了pico的文档和代码。

然后,在pico的文档中看见了这么句话:
引用
With a trivial example such as this there is no point in using PicoContainer. This was just to illustrate the basic API. PicoContainer becomes useful with larger number of classes and interfaces having complex dependencies between each other

终于让我长出了口气,原来,pico确实只是对应构造复杂对象网络的。程序内部的简单对象创建,尽管用构造函数,静态工厂,抽象工厂,jndi,不必要也不应该麻烦pico。


但是我还有几个疑问:
1。pico文档只给了些简单例子,比如用registerComponentImplementation(Class)来注册类,用getComponentInstance()来取得实例。看来对应一个
class C{
  C(A a1, A a2);{...}
}

这样的一个类,pico构造的时候是等价于new C(new A(), new A())这样呢?
还是final A a = new A(); new C(a,a);这样呢?
java不是一个引用透明的语言,pico怎样决定是否共享呢?


2。文档只给出了通过公有构造函数来创建组件的方式。那么假如某一个类型需要用静态工厂函数构造怎么办?(比如我的C的构造函数期待一个java.text.DateFormat的参数,你怎么构造这个参数?一个java.sql.Connection又如何?),
如果实际需要从某一个抽象工厂生成这个类型,又如何? 要调用jndi怎么办?
pico号称无侵入性,那么就应该允许组件以自己最合适的方式创建,而不是要求组件必须提供公有构造函数这么苛刻的条件。



这些问题我在pico文档中找不到答案,于是只好掳胳膊挽袖子读起pico的源代码来了。

昨天花了一个晚上的时间,大致走马观花下来,基本上对自己的疑问都找到了答案。

对问题一,pico给的解决方案很简单,它缺省采用了缓存策略,所以对任意一个component key,它只创建一个实例。
所以,上面的肯定是new C(a,a)这种。

只不过,我们无法保证这种策略永远是用户需要的,所以,它的DefaultPicoContainer接受一个ComponentAdapterFactory对象来保证对这个行为的修改能力。
但是,这个方法仍然要求容器内所有的组件采取单一的构造策略,要不就全缓存,要不就全创建新的。

而且,ComponentAdapterFactory是一个相当肥胖的接口,它虽然只有一个方法,但是这个方法承担的责任太多,实际上,一个ComponentAdapter几乎就占去了整个框架的一半以上的责任。它需要根据自己组件的需要在容器中查询所有它依赖的其它组件并创建组件。而pico容器说穿了就是一堆ComponentAdapter的查找表而已,肉都在ComponentAdapter里面呢。

把这样一个接口暴露出来仅仅为了修改是否共享的策略,嘿嘿,坏味道。

当然,pico提供了很多预定义好的ComponentAdapter的实现。直接用ConstructorInjectionComponentAdapter就可以达到上面改动策略的目的了。

一个可以考虑的替代方案,也许是单独做一个实例管理器,把组件之间依赖关系管理的责任从ComponentAdapter中剥离出来。这更合理,因为ComponentAdapter关心的单个组件,而实例是否共享,怎样共享,各个组件之间是否要通信等等,很多时候是整体的对象网络决定,而不是某个component自己能够决定的。而把关注点分离,也便于定制,客户实现InstanceManager肯定比实现ComponentAdapterFactory更容易了。


另外一个灵活些的方法是不用组件类型,而直接用ComponentAdapter来注册。同样,这个东西保持了最大的灵活性,但是同时把太多责任留给使用者。
使用它,可以实现我的第二个问题里面的用工厂啦,jndi啦,都可以实现。只不过,你需要自己从容器中主动寻找自己依赖的其它组件,而不是容器主动根据你的需求来把组件交给你。(而这正是ioc模式所反对的)。这除了可以把这个组件注册进容器之外,已经从容器得不到太多的好处了。


对第二个问题,关于支持更灵活的对象创建方式。我感觉现在的pico实现还比较原始,完全应该把constructor, method统一起来,抽象成一个creator之类的东西。然后原来给constructor用的代码基本都可以重用给creator。
这样,对象可以从任意的constructor或者任意的函数调用得到,没必要死咬着构造函数不放。



除此之外,说说读代码的感受,
总体上还行,变量名子很长(让我惭愧,我的变量名子都较短)。
也没有很面条的代码。

不过,文人相轻嘛,看别人的代码总是不舒服的。记得好像gigix说过pico就几百行代码,似乎是花小钱办大事的典范。其实我看也未见得。pico做的事情用伪码写起来不过是:
register:
  adapter = create adapter for the impl.
  add adapter to the hashtable.
getComponent:
   adapter = get adapter from hash table.
   adapter.newInstance

而基于构造函数的adapter这个最主要的实体,也不过是:
newInstance:
   constructor = find best constructor in the class.
    for each parameter type of the constructor:
       get the component instance of this type from container
    call the constructor with the resolved argument objects.

实在是没有什么太多事情要做。

而pico的代码量也不只几百行,DefaultPicoContainer加上ConstructorInjectionComponentAdapter以及他们的父类们代码两就上千行了。这还不说pico的代码很多行都超过了80个字符,本来应该折行的。

下面是几点具体的牢骚:
1。代码写的不够直接,继承关系有点过多过乱。这种通过子类override父类方法的template method设计让我觉得有点不舒服,好像回到了从前mfc的时代一样。好几个protected方法(据说很多人反对protected,我恰好也在其中),很多时候看一个方法到一半,它就调用super.thismethod(),然后还要跳到父类去看看它干了些什么,然后再回到子类接着看。反正看起来挺累的。
父子类这种关系很容易成为双向依赖的关系,而接口组合往往只是单向,所以对pico代码不用组合代替继承有点不满意。

当然,我没有时间仔细思考更好的办法是什么,反正是闻起来味道不好。

2。一些成员变量应该设计成final的。看见一个非final的变量,你总要想这个东西说不定在哪里给变掉了。给阅读造成困难。比如DefaultPicoContainer得parent变量,本来哪里也没有改动它,为什么不设计成final?

3。共有类太多,感觉api的设计比较庞杂,不简练。

4。那个life-cycle不伦不类。Startable和Stoppable这些接口对组件造成了侵入性。而且,这些start/stop本来和pico是正交的两件事,pico非要把这个东西弄进来干什么?
而且一个对象的生命期就是start/stop?就没有prepare? 没有cleanup?没有flush()?
要不要设计Preparable, Cleanable, Flushable, XXXable?
本来不过就是根据构造顺序遍历这些组件的一个foreach,一个internal iterator而已,弄个callback, 比如这样:


public interface Callback{
  void call(Object comp);;
}
void fifo(Callback cb);;//根据构造顺序。
void filo(Callback cb);;//反顺序

只要保证两种顺序就成了。客户如果自己有个什么MyStartable,要start, stop, flush, 客户自己adapt一个Callback对象然后调用fifo或者filo不就行了?比如:
container.fifo(new Callback();{
  public void call(Object comp);{
    if(comp instanceof Startable);{
      ((MyStartable);comp);.start();;
    }
});;

这里,start()甚至不需要是public的。不是好的多?

容器为什么要强迫组件实现Startable, Stoppable这些古怪接口?

5。使用reflection过头。pico要做的事情决定它必须依赖reflection,但是也不能什么都reflection啊。
reflection破坏静态类型安全,效率低下,也给调试造成困难(无法直接单步跟踪进reflection call)。
还是那个start/stop, 代码里居然用reflection来调用Startable.start(),搞什么?真是用上瘾了?

6。pico在处理多于一个构造函数的情况时,采取的是贪心法。找到满足条件的参数最多的构造函数。不知道是否有什么实际的考虑。
但是我个人的感觉是,这个策略会让程序结果有难以预料的倾向,降低了predicatability。
也许程序员随便增加了一个新的构造函数,谁都没告诉,编译器也没有提醒程序员这个构造函数被pico的绣球砸中了。但是居然程序结果就神秘地改变了!
我认为更合理的解决方法是只要有多于一个的公有构造函数就报错。
同时提供程序员更加细致的选择执行哪个构造函数的能力,比如:
registerComponentImplementation(Class c, Class[] param_types);;

这样,调用哪个构造函数能够做到心中有数,pico的代码也可以简单一些。
这同时也适用于调用其它函数来创建对象的情况。
   发表时间:2005-02-26  
引用
对问题一,pico给的解决方案很简单,它缺省采用了缓存策略,所以对任意一个component key,它只创建一个实例。
所以,上面的肯定是new C(a,a)这种。

只不过,我们无法保证这种策略永远是用户需要的,所以,它的DefaultPicoContainer接受一个ComponentAdapterFactory对象来保证对这个行为的修改能力。
但是,这个方法仍然要求容器内所有的组件采取单一的构造策略,要不就全缓存,要不就全创建新的。

而且,ComponentAdapterFactory是一个相当肥胖的接口,它虽然只有一个方法,但是这个方法承担的责任太多,实际上,一个ComponentAdapter几乎就占去了整个框架的一半以上的责任。它需要根据自己组件的需要在容器中查询所有它依赖的其它组件并创建组件。而pico容器说穿了就是一堆ComponentAdapter的查找表而已,肉都在ComponentAdapter里面呢。

把这样一个接口暴露出来仅仅为了修改是否共享的策略,嘿嘿,坏味道。

当然,pico提供了很多预定义好的ComponentAdapter的实现。直接用ConstructorInjectionComponentAdapter就可以达到上面改动策略的目的了。

一个可以考虑的替代方案,也许是单独做一个实例管理器,把组件之间依赖关系管理的责任从ComponentAdapter中剥离出来。这更合理,因为ComponentAdapter关心的单个组件,而实例是否共享,怎样共享,各个组件之间是否要通信等等,很多时候是整体的对象网络决定,而不是某个component自己能够决定的。而把关注点分离,也便于定制,客户实现InstanceManager肯定比实现ComponentAdapterFactory更容易了。


另外一个灵活些的方法是不用组件类型,而直接用ComponentAdapter来注册。同样,这个东西保持了最大的灵活性,但是同时把太多责任留给使用者。
使用它,可以实现我的第二个问题里面的用工厂啦,jndi啦,都可以实现。只不过,你需要自己从容器中主动寻找自己依赖的其它组件,而不是容器主动根据你的需求来把组件交给你。(而这正是ioc模式所反对的)。这除了可以把这个组件注册进容器之外,已经从容器得不到太多的好处了。


看起来很晕,能不能用代码说说(不要用伪代码!).
0 请登录后投票
   发表时间:2005-02-28  
我对ajoo的学习态度表示赞同!

基本上就看了你写的前面一截。但是基本明白你说的事情了。
ioc这种东西,知道它基本上是用来干什么的就好了。要用的时候再研究研究。
不要人云亦云,为了跟风而用它。
0 请登录后投票
   发表时间:2005-03-01  
ajoo 写道

只不过,我们无法保证这种策略永远是用户需要的,所以,它的DefaultPicoContainer接受一个ComponentAdapterFactory对象来保证对这个行为的修改能力。
但是,这个方法仍然要求容器内所有的组件采取单一的构造策略,要不就全缓存,要不就全创建新的。

一个可以考虑的替代方案,也许是单独做一个实例管理器,把组件之间依赖关系管理的责任从ComponentAdapter中剥离出来。这更合理,因为ComponentAdapter关心的单个组件,而实例是否共享,怎样共享,各个组件之间是否要通信等等,很多时候是整体的对象网络决定,而不是某个component自己能够决定的。而把关注点分离,也便于定制,客户实现InstanceManager肯定比实现 ComponentAdapterFactory更容易了。

容器可以有上下级关系, 一个容器以及它内部注册的组件可以访问它的父容器注册的组件(反之则不行):

PicoContainer parent = new DefaultPicoContainer();;
PicoContainer child = new DefaultPicoContainer(new PoolComponentAdapterFactory();, parent);;
parent.registerComponentImplementation(SingletonComponent.class);;
child.registerComponentImplementation(PoolableComponent.class);;


通过这样的想法, 偶们可以做有容器继承关系的复杂对象网络了.

实现ComponentAdapterFactory就一个方法, 麻烦在实现ComponentAdapter, 不过Pico提供了DecoratingComponentAdapter供扩展:

public class PoolComponentAdapter extends DecoratingComponentAdapter {   
    public Object getComponentInstance(PicoContainer container);{
        //...
    }    
}


ajoo 写道

不过,文人相轻嘛,看别人的代码总是不舒服的

好像蛮有道理, 写代码的和写文字的现在工作方式和工作性质都差不多了, hoho
做容器, 做架构这种东东, 不是偶的专业, 偶是做应用的, 只知道这些容器, 这些架构如何用就行了, 如果它们不好用, 要么改进它, 要么找找更好用的, ajoo大大你后面的一堆意见不妨提到pico mail list上问问看.
0 请登录后投票
   发表时间:2005-03-05  
Readonly 写道
ajoo 写道

只不过,我们无法保证这种策略永远是用户需要的,所以,它的DefaultPicoContainer接受一个ComponentAdapterFactory对象来保证对这个行为的修改能力。
但是,这个方法仍然要求容器内所有的组件采取单一的构造策略,要不就全缓存,要不就全创建新的。

一个可以考虑的替代方案,也许是单独做一个实例管理器,把组件之间依赖关系管理的责任从ComponentAdapter中剥离出来。这更合理,因为ComponentAdapter关心的单个组件,而实例是否共享,怎样共享,各个组件之间是否要通信等等,很多时候是整体的对象网络决定,而不是某个component自己能够决定的。而把关注点分离,也便于定制,客户实现InstanceManager肯定比实现 ComponentAdapterFactory更容易了。

容器可以有上下级关系, 一个容器以及它内部注册的组件可以访问它的父容器注册的组件(反之则不行):

PicoContainer parent = new DefaultPicoContainer();;
PicoContainer child = new DefaultPicoContainer(new PoolComponentAdapterFactory();, parent);;
parent.registerComponentImplementation(SingletonComponent.class);;
child.registerComponentImplementation(PoolableComponent.class);;


通过这样的想法, 偶们可以做有容器继承关系的复杂对象网络了.

实现ComponentAdapterFactory就一个方法, 麻烦在实现ComponentAdapter, 不过Pico提供了DecoratingComponentAdapter供扩展:

public class PoolComponentAdapter extends DecoratingComponentAdapter {   
    public Object getComponentInstance(PicoContainer container);{
        //...
    }    
}



这个parent关系也是一个我要抱怨的。
1。功能上,有了上下级关系就可以完全取代不同的caching策略?
cache与否本来应该是组件粒度的,现在你给我提供一个容器粒度的替代品,是想让我每有一次不同的策略都要单独做一个容器,然后把这些容器组成复杂的容器网络?
可不可行还不说,至少相当繁琐。
2。实现上,pico的DefaultPicoContainer把parent直接作为一个数据成员。它把容器间关系的管理和容器内部组件的管理都混在了一起。
这样造成逻辑复杂,更灵活的扩展(比如一个容器同时继承两个父容器)就做不到了。
更好的办法是一个SimpleContainer单独只处理内部的组件之间的关系,负责管理依赖。
而另外再做一个InheritContainer之类的来管理组件间的父子关系。
我现在在做一个与pico类似的支持auto-wire的ioc容器,就是这个思路。自觉比pico灵活,也简单。现在初步测试也已经通过了。






Readonly 写道

好像蛮有道理, 写代码的和写文字的现在工作方式和工作性质都差不多了, hoho
做容器, 做架构这种东东, 不是偶的专业, 偶是做应用的, 只知道这些容器, 这些架构如何用就行了, 如果它们不好用, 要么改进它, 要么找找更好用的, ajoo大大你后面的一堆意见不妨提到pico mail list上问问看.


我本来是想参加进去,把对静态函数的支持加上(通过把构造函数和静态函数都抽象为一个函数)。
但是当我看到DefaultPicoContainer的parent,觉得如果要改的东西太多就没有太多意义搀和进去了。我想改的是它的基本结构,不只是添加几个功能。


除此之外,再挑几个眼:
1。pico的构造函数的解析用贪心法。这对配置的可预测性造成了坏的影响。(这我前面也说过了)

2.DefaultPicoContainer.start()依赖于容器内的每个组件都是cached,这样它才不会每次调用start都生成一个新的组件实例。
而这个invariant很容易被当作参数传递进构造函数来的component factory打破。那时候,每次start()都创建了新的一个组件,而原来的组件并没有被start()。(这点我没有测试,是看的代码。如果我哪里看错了,欢迎指正)

3。pico不支持程序手工选择某一个重载了的构造函数。而只是自己“猜”。

4。spring的网站上有些文章对pico这种auto-wiring的容器的批评是:它只适合简单的情况,如果一旦情况复杂,同一个类型的许多不同实例,那么pico即使能够处理,付出的复杂性代价也不轻。我同意这点。
实际上,我实在有点怀疑,这种auto-wire的用途有多大。简单的对象网络,用explicit的在配置文件中配,比如用xml或者脚本语言(如jaskell),也没什么麻烦的。复杂的对象网络,确实用手工配比较繁,但是auto-wire能行吗?
new C(new A(1);, new A("hello");;

这种东西,不可能出现吗?是不是出现一个就要自己写ComponentAdapter?如果这样,只怕最终的代码会更加复杂晦涩,得不偿失。




最后我有一个问题,就是谁真真正正地在项目中用过pico吗?效果如何?
0 请登录后投票
   发表时间:2005-03-08  
我只用spring,因为spring还提供了其他好处。
pico只是一个微核心,应用pico的场景估计应该是如下场景是,自己需要一个容器,其中关于如何组织组件交给pico完成,更为特殊的需要,自己扩展吧。
exo算是一个例子。
对于一般应用可能会直接利用nano里面的冬冬吧。
0 请登录后投票
   发表时间:2005-03-09  
但是你用spring不就意味着要接受它的setter injection吗?
对很多简单对象写很trivial的getter和setter很烦啊,还是直接用构造函数省事。

我的感觉,spring只适用于粒度非常粗的组件,因为它的setter注定了你不能用immutable,注定了你的设计就不能是所谓:resource allocation is initialization.

能不能问个问题?据你看来,spring的xml配置是可选的还是紧紧绑在spring上的?我能否用某种脚本语言来替换它,而仍然使用spring的其它部分?
(比如,用groovy来配置spring的o/r,但是o/r的实际功能仍然是spring提供)

pico的对组件的自动组装在我看来有点不切实际,自动组装必然损害predictability。一个组件被组装了不正确的零件这种意外有可能会悄然发生。你恐怕总是需要显式地对特定的组件选择特定的参数。而如果这种定制过多的话,pico代码反而更加臃肿,难读。
0 请登录后投票
   发表时间:2005-03-09  
spring的xml配置是可选的还是紧紧绑在spring上的?
当然是可选的。我想只有xmldb是绑定在xml之外...

spring也有自动组装,可以by type,by name
引用

我的感觉,spring只适用于粒度非常粗的组件,因为它的setter注定了你不能用immutable,注定了你的设计就不能是所谓:resource allocation is initialization.

这句没看明白!
0 请登录后投票
   发表时间:2005-03-09  
看看pico的网站就明白了。
setter有个讨厌的问题,就是对象多了好多莫须有的中间状态(比如new之后,还没有set之前),对象实际上都是处在一种不一致的状态。这对调试程序是个不大不小的问题。你总不能在代码里处处都
if(var==null){...}
else {...}
这样吧?

而且哪个property是mandatory,哪个optional也看不出来。

immutable对象的好处很多人很多书都说过。我的总结很简单,就是:immutable对象简单,健壮。
0 请登录后投票
   发表时间:2005-03-09  
有时候会需要setter这种方式的,无论出于什么目的和设计思路。
使用spring其他好处是是指它提供了:jdo,hibernate的支持,aop支持等等。顺带就使用spring完成组件组装了。
我还真没有注意过是可选还是绑定的,pico一定是可选的。
尽管组件的自动组装理论上会有问题,但由于它会减少很多配置代码,这在一些开发者中会很受欢迎。(知道有问题,但诱惑又很大,我还是会考虑选择。)
对于pico的扩展并不会使pico更大,pico还是自己一小块。但扩展部分确实可能变的难看,但这部分如果不通用,也就算了。
我理解pico会和hivemind在使用上有些相似,都是内置到某些项目中,藏在代码中间,这就要求pico足够简单有效就行了,而使用这些项目的人并不需要知道。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics