锁定老帖子 主题:基础知识: 需求!
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2004-08-22
这个帖子好像破javaeye论坛的记录了吧?? 嘿嘿, 偶来继续贡献.
偶来用实例证明一下potian的观点: potian 写道 我要证明的就是,任何你在这个静态方法里面加入的东西,如果不光光是new ,它都会影响到这个类的重用 首先, ajoo认为, 他的immutable的singleton例子是对外部透明的, 所以可以放在类自身的静态工厂函数里面: ajoo 写道 好吧. 现在让我再解释一遍为什么这不是我的静态工厂的意图. 还记得我说的: 只有对外部透明的东西, 你从外部看不出任何语义上的区别的才是应该放在工厂里的? 而我那个对无状态,immutable的singleton的例子, 以及GenericHello的例子,都属于你不需要关心的。 如偶所知, singleton有N种实现的方式, 那么假设ajoo选择的是这样写: public X implements I { private static final X singleton = new X();; private X (); { //blah blah...... } public static I instance(); { return singleton; } } potian和readonly又分别开始使用这个package做项目了, 但是多事的readonly又发现, 使用这个package的时候, 启动内存占用得比较多: 程序一启动, 就构建了一个X实例, 在看了ajoo的代码以后, 于是小心翼翼地对ajoo说: 你的X.instance()代码, 能不能改成lazy load? ajoo觉得是一个合理的建议, 就修改了一下: public X implements I { private static X singleton; private X (); { //blah blah...... } public static I instance(); { if(singleton == null); { singleton = new X();; } return singleton; } } 于是发布了1.1版本, 但是这个时候potian却发现使用了新版本以后, 他的系统性能下降了一些: 因为每次调用X.instance, 都比原来多做了一次判断. 于是他要求ajoo继续保持原有的特性. 由于ajoo预先假设了他这样使用静态工厂方法的singleton实现是对于外部透明的, 试问:ajoo如何修改才能同时满足potian的系统高性能要求和readonly的低启动内存要求? |
|
返回顶楼 | |
发表时间:2004-08-22
我还有一个观点就是
不修改代码比修改代码要好,特别是应该尽量避免修改已经在运行的代码,特别是对那些处于我们业务核心的代码 |
|
返回顶楼 | |
发表时间:2004-08-22
由于ajoo大概是美国东部时间,和我们有时差,现在正在睡觉中,所以我们现在还看不到ajoo对readonly给出来的最新的例子代码的意见,不过根据ajoo的 “ 工厂内的代码实现应该对外部透明”,“外部所关心的实现是不透明的,不应该放在工厂里面的”这样的原则,我们来审视一下readonly的例子。
readonly 写道 由于ajoo预先假设了他这样使用静态工厂方法的singleton实现是对于外部透明的, 试问:ajoo如何修改才能同时满足potian的系统高性能要求和readonly的低启动内存要求?
现在potian用户在关心系统高性能,而readonly用户在关心低内存启动要求,这意味着对象被创建出来时机是外部用户很关心的事情,既然对象的创建时机已经被外部用户所关心,那么按照ajoo的封装和解藕原则, 引用 if(singleton == null) {
singleton = new X(); } 这样的代码就必须挪到工厂外面去。 因此我们可以看到,在这种情况下,静态工厂又等同于new了。 让我们回到readonly这个例子中的开始部分,也就是readonly的Singleton 1.0版。我再引用一下给大家看看,避免误会: public X implements I { private static final X singleton = new X();; private X (); { //blah blah...... } public static I instance(); { return singleton; } } 在readonly刚开始使用这个类的时候,他并不关心启动内存占用问题,随着他编写好的程序在使用中暴露出来这个问题的时候,他变得开始关心启动内存占用问题了。请注意这句话:“readonly开始没有意识到他应该关心内存占用问题,但他的应用启动内存多,客户开始抱怨了,于是他变得开始关心启动内存占有问题了” 这意味着什么?对于ajoo来说,这意味着客户需求的改变!由于客户需求的改变导致用户开始关心对象被创建的时机了,所以lazy代码是客户关心的代码,不能够被放在静态工厂里面。 于是ajoo对readonly说,你的需求我不能够放在静态工厂里面,你自己在工厂外面写好了。于是ajoo的X类拒绝做任何修改,instance里面仍然只是一个new,而readonly必须修改他的调用代码,来实现他的低启动内存方案,这意味着他原来的调用代码是: I i = X.instance();; ...... 现在,ajoo说了,你关心对象被创建时机了,这意味着对象创建对你不透明了,所以应该由你来创建,而不是我来创建,于是ajoo拒绝修改X的代码,readonly只好自己实现lazy,修改如下: if (i == null); { i = X.instance();; } ... 所以我们可以看到,按照ajoo的原则来分析readonly的例子,最后的结果就是: 引用 静态工厂 == new
这也从一个实例的角度证明了potian和robbin在前面的话,由于ajoo这个前提的存在,导致实际项目中,静态工厂里面只能有一个new存在,任何非new的语义在ajoo的原则下,都将被清除出去。 所以这个问题争来争去,也就是一回事。ajoo从理论上证明了静态工厂有比new更加严谨的优势,potian,readonly等人从实证的角度说明了ajoo认为优势的静态工厂最后必然退化为ajoo认为劣势的new。所以ajoo提出了一个对于学术理论很有意义但是在实际环境中毫无意义的观点。 |
|
返回顶楼 | |
发表时间:2004-08-22
不过,我觉得这里大家对"透明"这个词的理解肯定有分歧。
透明是不是就是下面这样不带参数的: Dog instance(); 还是: Dog instance(Color color); 这种带参数的? 对于前者,肯定没有异议。 那后者呢?属于哪种程度上的透明? 假设Dog接口中存在queryColor()这个方法,则依据不同的color,返回的Dog在语义上肯定有所不同。但是如果直接调用者本身也不关心Color是什么,比如根据properties中的信息生成color对象, 那么,这个color对它而言是某种程度的透明:它知道这个Color的存在,但并不关心具体究竟是那种color。或者说,不透明(依赖)的是Color接口,但透明的是Color的具体子类。 |
|
返回顶楼 | |
发表时间:2004-08-22
cat 写道 potian 写道 我这样翻来覆去是为了什么呢,我是为了证明,从纯粹的逻辑推理角度来讲,静态工厂不及公共构造函数灵活,除非静态工厂=new 事实上这个不能“证明”,只能“证否”。ajoo那个Predicate的例子就是一个“证否”:TruePredicate没有状态,所以Singleton之后丝毫不影响外界的使用,完全透明,且不等于new 但是我觉得,这种情况是否多到让广泛使用静态厂代替构造器才是一个问题。我认为多数情况可能不值,但在确实需要的时候适当使用确实是有益的。 这样的话静态工厂的适用场景被弱化到Singlton了。即使可能还有别的场景,但最终都将归结到无状态,无状态才能保证外部不可区分对象的语义。而且,这个再讨论下去,会变成FlyWeight模式。就又扯远了。 另外一种情况是,即使对象有状态,但从返回的接口类型不能区分出对象的这种状态。比如Dog没有queryColor这样的方法,并且所有其他方法的处理都与color无关。这就有点像实现多接口时upcast了: interface ABC extends A,B,C{...} 拿到一个ABC类型的对象,upcast成A类型,此时,如果A,B,C正交,client自然就区分不出只与B,C相关的状态。或者说,只要client不downcast,那么此时不同状态的ABC对象在语义上是不可区分的。 但是,这样的场景,用静态工厂来搞,是不是有意义。 |
|
返回顶楼 | |
发表时间:2004-08-22
介绍一点点这个讨论的旁支:
关于这个问题的讨论,主要分为两种对立观点: 来自abp(http://www.allaboutprogram.org)的版主ajoo持一方观点, 来自本论坛的potian,ozzzzzz,logo,readonly,charon,firebody,持另一方观点。 同时讨论得到了来自abp论坛的版主Elminster,cat等人的关注,显然,他们不愿意在这里置疑ajoo的观点,于是他们在abp另开讨论场所。 在这里看的晕头转向的可以到那里再看看,从来自不同背景的人的角度看待问题,具有一定的启发意义。 http://www.allaboutprogram.com/viewtopic.php?t=2209 |
|
返回顶楼 | |
发表时间:2004-08-22
好的,现在大伙都到了收尾阶段了!我开始做我个人观点陈词:
1)我认为一个可发布的类,尽可能的符合bean规范,以达到可装配的目的。 而如果象ajoo所说的private构造器,显然失去了bean规范的合约,也失去了Bean组件的优越性,这是我不能容忍的。 2)假设用static instance代替构造器,当要发布的类关联着其他的类时,为了实现ajoo所说的语义完整的实现,意味着instance中必须能够提供具体关联类的instance。如这个例子: public class Service{ public A getA();; public void setA(A a);; public void execute();; } public class ServiceImpl implements Service{ private ServiceImpl(A a);{ setA(a);; } public static Service instance();{ this(Aimpl.instance(););; } } 看看ServiceImpl的instance,为了ajoo所说的对象产生的语义完整,instance确需要Aimpl具体类的耦合,具体类之间如此紧密的耦合,是我们最不愿意看到的。 昨晚,当我提出这个观点反驳ajoo时,ajoo这样回帖: |
|
返回顶楼 | |
发表时间:2004-08-22
ajoo 写道 好了. 目前总结出来一个观点:
class BlackDog implements Dog{ ... BlackDog();{...} }和 class BlackDog{ static Dog instance();{return new GenericDog(new Black(););;} } 这两个类的具体含义不同, 所以后者叫BlackDog是不可以的. 所以坚决是要换名字的. 客户端原来如果是BlackDog.instance(), 现在必须变成BlackDogFactory.instance()才合道理. potian, firebody, 你们是否再这点上一致? 好吧, 先不说这个观点是否是真理吧. 我们保留这个争议. 作为讨论到现在的反对方的一个论据. 如何? 我们先弄清楚了双方都有些什么观点, 至于每个具体点大家是否意见一致并不十分重要. 这也是我拿手的, 列出所有优缺点, 然后大家自己比较权衡. 这个只是说明那个静态厂instance()返回接口并不存在重构上的灵活性。 其实更加简单的说法是,如果静态厂A的instance()重构后返回的不是A的实例,而是实现了所需接口类型的另外一个类的实例。 这样,因为这个A的构造函数是私有的,同时instance返回的是别人的实例,那么,A自己的实例别人也永远得不到。 最后,A里面除了静态方法以外,定义别的方法都没有意义。 这时,除了那种聚集了大量静态方法的工具类之外,别无意义。而在这里,因为只有一个instance()方法,存在必要性几乎变成0了(没有变成0是为了不改动客户代码)。 |
|
返回顶楼 | |
发表时间:2004-08-22
cat 写道 提醒一下,ajoo说instance中做事情需要对外界透明,实在很早就提出了: ajoo 写道 我也不必每次调用instance()函数的时候都new, 随着设计的演变也许某天我发现一个singleton就够我用的了.
http://forum.iteye.com/viewtopic.php?t=6836&postdays=0&postorder=asc&start=0 这句话我第一次读的时候就感觉隐含了这个意思,当然,表述显然不够明确。 第一次明确提出,是在这里: http://forum.iteye.com/viewtopic.php?t=6836&postdays=0&postorder=asc&start=39 ajoo 写道 关于我提的singleton,可能有些误解。
我说的singleton, adapter等模式的重构,完全是基于X的实现细节变化的。 比如, case 1: 通过重构,我发现类X本身不需要持有不同状态。那么,每次都new一个就是浪费。所以要改成singleton。 注意,这里的singleton的决定不是因为我的客户希望用单独的实例,而是我因为看到X的实现不需要不同的实例而做的优化。这是跟X的内部实现紧密耦合而对客户透明的。不要和非透明的,外部因为希望使用单独实例而引入的singleton混淆。 case 2: 本来写好了X1, X2, X3类,实现了接口I的功能。但是通过重构,我们发现有很多的共同点可以抽出来,于是我们写了GenericI1, GenericI2两个更可重用的类。如果要求客户程序改用GenricI1, GenericI2,不免波及太大。 所以我们希望X1, X2, X3在此时只是充当一个客户程序的入口。 这几句话再明确不过了,或许有些人认为还是不够清楚?我觉得可能是看帖的时候滑过去所致。从这篇帖子到现在,可能100帖都不止了,还有那些掐架什么的,开了3个话题,实在有点浪费。交流,首先要了解对方的思想,否则只是单方向的,不可称之为交流。 这个,只怕是您从最直白的字面上来了解ajoo同志的"透明"含义了。 我的理解,ajoo所讲得更多的是指语义上的透明性,这个从他坚持instance返回接口类型就可以看得很清楚。 |
|
返回顶楼 | |
发表时间:2004-08-22
Readonly 写道 public X implements I { private static final X singleton = new X();; private X (); { //blah blah...... } public static I instance(); { return singleton; } } potian和readonly又分别开始使用这个package做项目了, 但是多事的readonly又发现, 使用这个package的时候, 启动内存占用得比较多: 程序一启动, 就构建了一个X实例, 在看了ajoo的代码以后, 于是小心翼翼地对ajoo说: 你的X.instance()代码, 能不能改成lazy load? ajoo觉得是一个合理的建议, 就修改了一下: public X implements I { private static X singleton; private X (); { //blah blah...... } public static I instance(); { if(singleton == null); { singleton = new X();; } return singleton; } } 于是发布了1.1版本, 但是这个时候potian却发现使用了新版本以后, 他的系统性能下降了一些: 因为每次调用X.instance, 都比原来多做了一次判断. 于是他要求ajoo继续保持原有的特性. 由于ajoo预先假设了他这样使用静态工厂方法的singleton实现是对于外部透明的, 试问:ajoo如何修改才能同时满足potian的系统高性能要求和readonly的低启动内存要求? 我认为ajoo在前面讨论的都是基于语义的,而在结论中的透明也是针对语义的透明,当然,没有说明确是ajoo的事。对于实现的任何细微的修改,都会或多或少影响到性能,找这个思路下去,以性能为借口,可以威胁封装原则。这是两个不同层面的问题。 |
|
返回顶楼 | |