论坛首页 Java版 Spring

就说pico

浏览 24089 次
锁定老贴子 主题:就说pico
该帖已经被评为精华帖
作者 正文
时间:2004-08-14
我没有使用过pico,只是看了一写pico的介绍和示例代码.
所以如果问题看起来比较蠢,也许就是真的蠢了.

首先,自己坦白交代一下,

我这个人喜欢接口喜欢得没边,凡是直接用class的地方我就要瞪着眼镜看半天.

所以,我的class多数都是final的, 整个接口体系可能是一个较深的树,但是类体系非常扁平,很少有超过三层的继承,而且即使继承也是在package内部, 外面的用户根本就不给继承我的权利.

另外,我的构造函数多数也都是私有的.

说到这里,你可能觉得奇怪了: 你构造函数私有了,我怎么用啊?


在回答这个问题前,让我先列举构造函数几个罪状:
1. 构造函数没有自己的名字,只能使用类的名字.如果我有若干个重载版本的时候,可读性变差,而且有时候不容易用参数类型区别两个用意不同的构造函数.

举例:

[code:1]
final class C{
public C(int i);
public C(int i, int j);
public C(int i, int j, int k);
public C(String s, int i, int j);
public C(Object o, int i, int j);
}[/code:1]2. 构造函数告诉调用者:我会给你新构造一个类C的对象. 这暴露的细节太多了. 如果用户真正需要的只是一个I的接口,为什么我要告诉他那么多他不关心的细节呢?
而且,如果我忽然发现我的类C可以用decorator, bridge, adapter, singleton或者flyweight来重构一下, 构造函数根本不给我机会. 要加这些东西,就要改动客户代码.

举例:
[code:1]interface I{
...
}
class C implements I{
public C(int i);
}


client code:

final I x = new C(1);[/code:1]

[code:1]
某天我重构时突然发现,啊呀,我可以把功能委托给一个更一般的实现GenericC或者NativeC上.
我的C1, C2, ... Cn类唯一需要做的只是把输入的参数稍微做一下整理再根据一定的逻辑传递给GenericC或者NativeC.
class C implements I{
public static I decorate(int i){
if( i< 100){
final I x = new GenericC(new Integer(i));
x.setXXX(i);
return x;
}
else{ return new NativeC(i); }
}
}[/code:1]

这时,客户使用new C()的代码的已经不得不更改.我的设计的弹性到此为止.


3. 构造函数没法互相调用.这样,我如果有一些要重用的代码, 就不好在各个不同的构造函数之间重用了.
你也许会说用一个函数, 让每个构造函数都调用这个函数.
但是, 很多时候这些公用的逻辑可能都和"构造"相关,比如说:给某些final成员赋初始值.这些一个公用的函数做不到.


其实,说穿了,什么是构造函数?不过是一个语言规定了名字和返回类型的静态工厂函数而已.你要是用delphi, 就发现delphi的构造完全就是通过工厂函数来的.

所以,我的类多数都是私有构造函数(我的构造函数都非常简单,一般就是有几个成员变量就有几个参数,然后一个一个初始化就是了).
然后通过若干个静态的工厂函数来对外公开.

例子:
[code:1]
final class MyC implements MyI{
private final int i;
private final Comparator c;
private MyC(int i, Comparator c){
this.c = c;
this.i = i;
}
public static MyI instance(int i, Comparator c){
return new MyC(i, c);
}
public static MyI instance(int i){
return instance(i, new DefaultComparator());
}
public static MyI instance(int i, String s){
return instance(i, new NamedComparator(s));
}
}[/code:1]

注意,我的返回类型都不是MyC, 而是MyI. 这样当我重构时,我可以随意地用各种pattern调节. 比如我的MyC不需要再implement MyI了.无所谓, 客户根本不依赖这个,我只要保证这几个工厂函数返回的是一个MyI就是了.

我也不必每次调用instance()函数的时候都new, 随着设计的演变也许某天我发现一个singleton就够我用的了.


好了,不再自卖自夸了.

你也许猜到了,我的标题是"就说pico",我要说的就是和静态工厂有关.

pico似乎非常依赖构造函数. 那么对我这样一个构造函数的反动者,它能帮我么?

理想情况下,我希望在xml文件里面除了指定要构造的类的名字,还能指定工厂方法的名字.它能做到这点吗?

我知道pico本身并不理会xml配置.那么从pico本身来说,它能够register工厂方法吗?
   
时间:2004-08-14
Spring最新版本允许从工厂方法创建bean,我就说这个特性很有用,是容器应该具备的。Pico好久没去关注了,不知道它的最新版本有没有这个特性,如果没有,也是个小缺憾,别太求全责备吧。
   
0 请登录后投票
时间:2004-08-14
ajoo 写道
注意,我的返回类型都不是MyC, 而是MyI. 这样当我重构时,我可以随意地用各种pattern调节. 比如我的MyC不需要再implement MyI了.无所谓, 客户根本不依赖这个,我只要保证这几个工厂函数返回的是一个MyI就是了.


接口的本意只是建立一个公共的通信协议标准而已,它强制实现这个接口的类遵守共同的承诺。但是对一个类来说,即使这个类不实现这个接口但是提供完全相同的公共类方法,这跟实现了该接口没有多大的区别,实现了该接口的类能提供的好处只是多提供了一个向上转型为接口类型的机会,在使用上,你看不到更多区别。对于隐藏实现的问题,只要你不提供超出方法原型的细节,我觉得使不使用接口这之间的区别几乎是没有的。接口隐藏了实现细节,但是隐藏实现细节并不是只有使用接口才能做好,这二者是不能反推的。

另,谢谢你,ajoo,我看了reflection.Proxy了,确实很好。我刚看了你的另一个帖子,你在犹豫的承认AOP的存在也许是需要的。看了proxy之后,我想到的问题是:java中的proxy是合理的,它做了它可以做的事,没有做它不该做的事,AOP却是做了proxy不该做的事。我这么说的原因在于,proxy是在java的类型保护模型的限制下出现的,它的所作所为不会超过类型保护模型(public/protected/private)的限制,这很合理,这保证了java的信任体系的完整性,并没有破坏它。但是AOP破坏了这个机制,AOP跳过了java compiler 的规则检查,自己另外实现了一套机制。这是对java保护模型的破坏,所以我现在想,在java里面实现AOP语言特性的可能性不大。
   
0 请登录后投票
时间:2004-08-14
看我那个decorate的例子。

在重构过程中,我完全可能考虑把MyI的功能直接委托给别的类,而我这个类什么也不做。如果你返回的不是接口就没有这个自由。
   
0 请登录后投票
时间:2004-08-14
ajoo, 你这是在误导观众, 看看你举得那个Decorator的例子:
ajoo 写道

这时,客户使用new C()的代码的已经不得不更改.我的设计的弹性到此为止.


使用工厂函数的话, 你肯定不是用new C(), 而是用
MyI myI = MyIFactory.create(1);
或者是按照你的例子, 把工厂函数写到实现类里面去
MyI myI = MyC.decorate(1);

而使用IoC容器的话, 那么就是
MyI myI = IoCContainer.getComponentInstance(MyI.class);

在这里MyIFactory和MyC的静态方法和IoCContainer所做的工作不就是一样一样的么? 只不过pico/spring把组装工作用统一的方法(代码或者配置文件)进行了处理.


另外偶理解的Decorator可不是象你这个样子的, 不过按照你的写法, 也可以写:
[code:1]
public class DecoratorC implements MyI {
private MyI myI;
public Decorator(i) {
if(i > 10){
this.myI = new GenericC(myI);
}else{
this.myI = new NativeC(myI);
}
}
}
[/code:1]

偶现在是越来越讨厌static方法的, 在写测试代码的时候, 你很难用MockObject来取掉有static方法的对象.
   
0 请登录后投票
时间:2004-08-14
用容器和new本质是一样的。我说的几个灵活性问题它们都有
1。我没有重构成singleton或者flyweight的自由。new对用户承诺了创建一个新对象。我不想做这个承诺。
2。我没有重构成adapter的自由。new对用户承诺:我给你的就是一个类X的实例。我也不想做这个承诺,我要保留用任何我认为合适的方式构造这个对象的权力。


另外,我不明白你说带static方法不好mock的问题。
任何抽象最后都要具体化,你new也好,newInstance也好,pico container也好,都不是容易mock的接口。从mock的角度,它们和static方法有什么区别?
如果你说X.instance()不好mock,那么new X()就好mock了?
我看不出这里面有什么区别。
   
0 请登录后投票
时间:2004-08-14
偶上面的例子不是已经说明了: new 返回的是Concrete Class, 而用工厂函数或者IoC容器创建, 你获得的是Interface, 你说的问题更本不存在.

关于MockObject, 偶的测试代码, 只用写一次, 以后如果需要测试真正的实现类组件, 只用修改配置文件, 换一个实现就好了, IoC容器和工厂方式相比, 这样就方便一点了.
   
0 请登录后投票
时间:2004-08-14
关键在于重构的自由度。
如果你是暴露构造函数,比如用new X(),或者PicoContainer.newPicoInstance(X.class);

我如果要求你把X变成singleton或者flyweight,你能不改你的X类以外的代码么?

我如果忽然告诉你:其实这些功能在Y类里面更好的实现了。为避免重复,你直接用Y好了。
此时,如果你不想改动客户代码,是否还要一行行地写委托代码?(想象一下给ResultSet写委托代码吧)
如果Y的签名稍微改动一点,你的委托代码是否也要改?
   
0 请登录后投票
时间:2004-08-14
Readonly 写道
:twisted: 偶上面的例子不是已经说明了: new 返回的是Concrete Class, 而用工厂函数或者IoC容器创建, 你获得的是Interface, 你说的问题更本不存在.

关于MockObject, 偶的测试代码, 只用写一次, 以后如果需要测试真正的实现类组件, 只用修改配置文件, 换一个实现就好了, IoC容器和工厂方式相比, 这样就方便一点了.

至少我知道的pico还是要求你有一个共有的构造函数的。所以我说的问题当然存在。

抽象工厂和这个问题不相关。我说的是怎样公开一个生成具体对象的方法。抽象工厂还是一个接口,你还是要面临是否公开实现这个抽象工厂的某个类的构造函数的问题。

而你说的所谓改动配置文件,正是我要说的:不支持静态工厂方法的容器还是太差劲!
不能让设计屈就于容器,容器应该服务于设计。
   
0 请登录后投票
时间:2004-08-14
来打个擂台吧。挺有趣的。我这里有一个类:

[code:1]interface Persistence{
void store(Object obj);
}
class JdbcPersistenceImpl implements Persistence{
JdbcPersistenceImpl(final Connection conn){...}
public void store(Object obj){...}
};[/code:1]

按照你的说法,你认为直接公开JdbcPersistenceImpl的构造函数就好了。

不错,你的客户代码可能是:

final Persistence pers = new JdbcPersistenceImpl(conn);

或者:
[code:1]final Persistence pers = (Persistence) PicoContainer.newPicoInstance(Persistence.class);[/code:1]
无论如何吧。

下面,我开始重构了。

首先,我进一步的研究发现,JdbcConnectionImpl是个比较昂贵的对象,最好可以用flyweight模式,避免对同一个connection构造不同的JdbcPersistenceImpl对象。(就是内部存一个HashMap了)

请问,你如何在不改动客户代码的情况下实现这个功能?

别的暂时先不谈。咱么一步一步来。
   
0 请登录后投票
论坛首页 Java版 Spring

跳转论坛:
JavaEye推荐