论坛首页 Java企业应用论坛

基础知识: 需求!

浏览 110168 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-08-23  
呵呵,robbin描述得很形象
0 请登录后投票
   发表时间:2004-08-23  
我看了这个帖子后也有同感,感觉讨论的东西不make sense.
0 请登录后投票
   发表时间:2004-08-23  
引用

我看了这个帖子后也有同感,感觉讨论的东西不make sense


确实如此,这个贴子主要讨论的是一个逻辑问题,即不是一个java编程问题,也不是一个OO问题,最后我认为从纯逻辑的角度来说,静态工厂方法比new更严密。
0 请登录后投票
   发表时间:2004-08-23  
从纯逻辑的角度来说,静态工厂方法确实比new更严密。
但是在大多数情况下,这种严密性是多余的,new比静态工厂更简单、更值得信赖,new是由编译器提供保证的,但Instance只能由工厂作者提供保证。
0 请登录后投票
   发表时间:2004-08-23  
charon 写道

不理解,为什么要每个对象自己搞一个cache,为了节省内存?一个大的cache搞定应该可以了吧。那样,也不用唠烦gc了。
因为每次根据给定的i,计算得到的值都是一样的,那么多cache没有区别。如果说根据相同的i,不同的对象的calc方法可以得到不同的结果,那我认为这个自带cache的做法还有点意思。


我的意思就是节约内存,不然一个cache老在那里挂着,保持一定规模,貌似不太灵活。不知道ajoo什么意思。

我也觉得接下来就是实践的问题了。当然现在java中的new和静态厂地位不同,所以ajoo很有理由可以自圆其说。不知道前面提及的delphi中情况如何。总之观点基本已经明确,剩下的都是些靠三言两语的逻辑和现有实践都打不倒的东西了。等哪年ajoo自己写个什么语言里面静态厂作为创建界面大获成功了,结果实践中几乎所有静态厂都是new,或者静态厂的某一方面得到显现,才能算数。

就像java的exception specifications,不实践一下,真是看不出居然这么累赘。
0 请登录后投票
   发表时间:2004-08-23  
potian 写道
引用

我看了这个帖子后也有同感,感觉讨论的东西不make sense


确实如此,这个贴子主要讨论的是一个逻辑问题,即不是一个java编程问题,也不是一个OO问题,最后我认为从纯逻辑的角度来说,静态工厂方法比new更严密。


我也推崇静态工厂
最灵活,允许使用多个包里面私有类的实现
同时可以通过提高静态工厂的对象缓存提升性能

Effective Java Programming Language Guide是本好书,赫赫,我看了块三遍还是觉得有地方没有吃透
0 请登录后投票
   发表时间:2004-08-24  
那个Calc的例子确实不好。我举的时候就想到了:为什么不能让这个cache也是静态的?那样仍然不能说明从singleton重构到非singleton的可能性。
自己掌嘴!

我也想了一些加log,加trace,加profile之类的应用,但是这些无一例外都是最好用interceptor。

所以,看来也许必须从singleton转化成非singleton的情况只会出现在语义发生变化的时候。

不过,我仍然觉得这可以算做工厂方法的一个好处。就是:当你被迫修改现有代码来应对需求变化的时候,它同样帮助你把变化最小化,局部化。

我们大家都认为理想的情况是扩展而不是修改已有代码。但是我觉得这不是绝对的。
有的时候当需求变化了,而不是增加了,当保留原来的代码变得没有意义的时候(你当然可以把原来的代码存档。但是保留在工程中已经没有意义了),改变代码以适应需求变化比强求ocp要合理。
比如,原来黑狗叫一声,后来发现,应该叫两声,直接修改黑狗的代码我认为是最直接的也是最自然的手段。

说到黑狗,试图回答一下charon的这个黑狗非狗问题。
我觉得这是一个看问题的角度问题。如果从客户代码角度看,我只知道BlackDog.instance()给了我一个具有BlackDog语义的Dog实例。这就够了。
你BlackDog内部实现是真的直接实现了一个黑颜色的狗还是仅仅当作一个工厂从别的地方弄来的这个狗,我不关心。对我来说,BlackDog就是这个模块的入口。这个名字本身只是体现了这个模块的语义。我想这可以说得通吧?

不过这个例子里面的X1和X2自己本身所作的事情太少,以至给人一种空壳的感觉。所以charon才认为这个X1和X2应该改名字或取消的想法。
但是在其它一些场合,也许会有一些更多更实质性的逻辑要做的。

下面我举一个稍微复杂一点的例子。不过例子复杂了,上下文的交代就会比较多,就需要大家更多的耐性。

假设我们要做一个脚本解释器。我们有一个表达式要处理。接口如下:

public interface Exp{
  void accept(ExpVisitor v);;
}
public interface ExpVisitor{
  void visitMinus(Exp a, Exp b);;
  void visitNumber(int i);;
}
public interface ExpFactory{
  Exp buildMinus(Exp a, Exp b);;
  Exp buildNumber(int i);;
}

这里用了abstract factory和visitor。
实际上的表达式种类一般都不止两种,我这里简化了一下,只有数字或者减法。比如
1-1, (1-1)-(2-3)
都是合法的表达式。

下面,我的程序需要一个对表达式转换的功能。接口如下:
public interface ExpTransformer{
  Exp transform(Exp exp);;
}

就是说,给定一个表达式,把它转换为另外一个表达式。(这在编译器或者解释器中是很常见的)

然后,假设我的程序需要一个转换器,递归地把参加减法的两个子表达式互换。
如: 1-2变成2-1, (1-2)-(3-4)变成(4-3)-(2-1)
那么,张三写一个公共类如下:
public final class Flipper implements ExpTransformer, ExpVisitor{
  public Exp transform(Exp exp);{
    exp.accept(this);;
    return result;
  }
  public void visitMinus(Exp e1, Exp e2);{
    e1.accept(this);;
    final Exp a = result;
    e2.accept(this);;
    final Exp b = result;  	
    ret(factory.buildMinus(b,a););;
  }
  public void visitNumber(int i);{
    ret(factory.buildNumber(i););;
  }
  private Exp result;
  private final ExpFactory factory;
  private void ret(Exp e);{result = e;}
  private Flipper(ExpFactory f);{
    this.factory = f;
  }
  public static ExpTransformer instance(ExpFactory factory);{
    return new Flipper(factory);;
  }
}

逻辑就是对每个子表达式递归调用accept来转换顺序。

然后,我又有了一个需求,要求把一个表达式的每个参与数字做个乘方运算。例如: 2-3变成4-9, (1-5)-(3-4)变成(1-25)-(9-16)
李四写了下面的公共类来给系统提供这个语义:
public final class Squarer implements ExpTransformer, ExpVisitor{
  public Exp transform(Exp exp);{
    exp.accept(this);;
    return result;
  }
  public void visitMinus(Exp e1, Exp e2);{
    e1.accept(this);;
    final Exp a = result;
    e2.accept(this);;
    final Exp b = result;   
    ret(factory.buildMinus(a,b););;
  }
  public void visitNumber(int i);{
    ret(factory.buildNumber(i*i););;
  }
  private Exp result;
  private final ExpFactory factory;
  private void ret(Exp e);{result = e;}
  private Squarer(ExpFactory f);{
    this.factory = f;
  }
  public static ExpTransformer instance(ExpFactory f);{
    return new Squarer(f);;
  }
}


当然,例子还可以一直举下去。代码我就不写了。就假设我们有另外的几种Transform的语义。
好,没问题。除了某天一个牛人来视察的时候发现了浓重的坏味道:逻辑重复。

于是, 为了抽出共同点,我们做以下重构:
1。这些类不同的地方只是对叶子节点即非叶子节点的构造方式。
2。这些类相同的地方是都要对语法树遍历。

所以,我们先引进一个包私有的接口来封装不同点:
interface ExpMapper{
  Exp mapNumber(int i);;
  Exp mapMinus(Exp a, Exp b);;
}

然后,再做一个包私有的公共逻辑类,来抽取共同逻辑:
class GenericExpTransformer implements ExpTransformer, expVisitor{
  public Exp transform(Exp exp);{
    exp.accept(this);;
    return result;
  }
  public void visitMinus(Exp e1, Exp e2);{
    ret(mapper.mapMinus(transform(e1);,transform(e2);););;
  }
  public void visitNumber(int i);{
    ret(mapper.mapNumber(i););;
  }
  private Exp result;
  private ExpMapper mapper;
  private void ret(Exp e);{result = e;}
  private GenericExpTransformer(ExpMapper m);{
    this.mapper = m;
  }
  public static ExpTransformer instance(ExpMapper m);{
    return new GenericExpTransformer(m);;
  }
}

这个共同逻辑依赖的是一个更容易实现的接口ExpMapper。

最后,我们重构Flipper和Squarer如下:

public class Flipper implements ExpMapper{
  public Exp mapNumber(int i);{
    return factory.buildNumber(i);;
  }
  public Exp mapMinus(Exp a, Exp b);{
    return factory.buildMinus(b,a);;
  }
  private Flipper(ExpFactory f);{
    this.factory = f;
  }
  private final ExpFactory factory;
  public static ExpTransformer instance(ExpFactory factory);{
    return GenericExpTransformer.instance(new Flipper(factory););;
  }
}

public class Squarer implements ExpMapper{
  public Exp mapNumber(int i);{
    return factory.buildNumber(i*i);;
  }
  public Exp mapMinus(Exp a, Exp b);{
    return factory.buildMinus(a,b);;
  }
  private Squarer(ExpFactory f);{
    this.factory = f;
  }
  private final ExpFactory factory;
  public static ExpTransformer instance(ExpFactory factory);{
    return GenericExpTransformer.instance(new Squarer(factory););;
  }
}


两个类都还是各自拥有一定的逻辑,但是我们确实通过重构,抽取了共性,得到了更好的结构。

另外值得注意的是,ExpMapper, GenericExpTransformer这些东西都可以被隐藏在局部,虽然我最终返回的是一个GenericExpTransformer的实例,我仍然可以隐藏这个外界不关心存在与否的类。
这种隐藏具体类而输出类实现的语义的方法也是构造函数无法做到的。

oz说看不到实际应用中静态厂能做得到的,new为什么做不到。那么对这个例子,如果没有静态厂,是否外界就要关心我们现在引入了几个额外的辅助类,并且外界的调用方法也要从new Flipper()改成一个更复杂的new GenericExpTransformer(new ...);呢?

一些人也许认为只要通过配置文件保证了这个构造方法只出现在一个地方,那么改点调用代码不是什么大不了的事。

我的观点还是依旧:
1。我不认为我的对象构造肯定只出现在配置文件中是一个真理。它也许如你们描述的那样(当我这个东西是个粒度较大的组建,大家都是用ioc来避免和我直接依赖), 但是,我认为实际中无法排除不那么理想的情况。这个类可能不是什么组建,就是一个非常小的模块,别人可能认为直接依赖我不是什么大不了的事,只要保持单向依赖就够了。

2。即使在配置文件中改一下组装逻辑就可以了。我也不认为这是一个理想的方法。根据模块的内聚性,我的上面的重构完全是模块内部的事情,被迫影响到处在模块外部的配置文件,即使影响很小,我也认为这是一个结构上的失误。

3。既然从工程实践中考虑,改点调用代码不是什么大不了的事。那么我作为类的实现者,是否也可以这样想:
用工厂虽然稍微麻烦了一点,但是多写那两行代码也没什么大不了的。我还是对将来可能得到的不需要改动外部代码,不用担心项目经理对我吼这个好处更心动。而且就算用不上,也没关系呀。big deal? I don't think so.

另外,oz, 我觉得还是有一点误解需要说明:
你似乎认为我想用静态厂取代容器的地位。
不,这不是我要说的,如果谁跟我说这个,我没你那么耐心,直接就告诉他:胡说八道!
我一直要证明的都是:静态厂和容器是正交的关系。它们是相辅相成的。无论是这里一些人的观点说有了容器就不要静态厂,还是说有了静态厂就可以没有容器,都是看不出根据的。

我一直反对的是:用容器做静态厂应该做的事,或者用静态厂做容器应该做的事。
容器,老老实实做好你管理对象依赖关系的工作!不要越俎代庖。不要对组件的设计指手画脚。
静态厂,认认真真地做你的封装实现细节这份很有前途的职业!不要对使用你的容器或者是否有容器使用你做出任何假设。
0 请登录后投票
   发表时间:2004-08-24  
我不知道我能否举出更实用的例子,
不过,不知道谁看过effecitve java的,给贴两个那个书上的例子看看?
其实,这一系列的贴子的发展也挺出乎我的意料。

如果谁有兴趣考证的,可以发现,我最初本来不是试图在这里拼命推销静态厂,而是说:
哈哈,我喜欢用静态厂,因为好处一好处二,那么容器是否支持静态厂呢?
一个简单的实用性问题而已。

结果发展到现在,居然变成我要拼命找出例子来说服大家也用静态厂了。

robbin说看不出用它来指导整个设计的好处。我也看不出的。这个东西和很多其它pattern一样,都是一个很局部的解决方案。是否合适,要看局部的上下文。怎么能不分青红皂白用来指导整个系统的设计呢?如果谁那么做,我第一个跳出来指责他滥用工厂,就象我批评滥用容器一样。

要说指导整个系统的,也许只有面向接口这个大原则,但即使是这个,也不能走极端,任何两个模块,不论粒度多小都不能有直接依赖,都要通过接口耦合,在我看来也是不可取的。

不过既然话说道这里了,我倒也想试着说服几个人,改变一下对静态厂的态度。
上面已经举了一个例子,大家可以发表看法,看看是不是用静态厂的话确实有隔离变化的实际作用。是否如一些人斩钉截铁断定的那样:是没有实际意义的。

另外也希望读过effective java的同学们贴上一些书中的例子。
0 请登录后投票
   发表时间:2004-08-24  
至圣先师说中庸,中庸。

我开始反对的是一干人咬牙切齿要彻底制静态厂于死地,静态厂绝对不可用的极端观点。
现在则在反对别人把我放在“静态厂万岁!静态厂要完全取代构造函数,要当系统设计的主导思想,要把容器打回老家去”的火上烤。

什么东西都不是银蛋。容器不是,静态厂也不是。

静态厂,不错,价格便宜量又足;容器也不错,用起来挺爽的。
但是,什么东西滥用都会出乱子地。你拿人参当饭吃?活腻了吧?
0 请登录后投票
   发表时间:2004-08-24  
仔细想了一下。
觉得试图说服大家静态厂有多么实用很难。
如果哪位能够贴出Effective Java上的比较有说服力的例子,当然好。如果没有,我的能力有限,就不献丑了。

而且所谓“实用”在每个人的心目中的具体含义也不一定相同。这个人觉得实用的,那个人不见得这么觉得。
比如多重继承,有人觉得java不支持简直罪大恶极,但是我们倒是挺高兴它不支持的。
再比如interface,c++就没有这个东西,只有abstract class, 我觉得不可接受吧, 但是许多c++er偏不以为然。
再比如Delphi,语言就没有固定构造函数的名字。它的构造函数都是和静态工厂一样是程序员命名的。也没有人难受得跳楼。

反正我的目的已经达到。
1。证明了静态厂不是evil,不会因为使用了它而影响你的系统的灵活性。
2。指出了静态厂的几个优点
  a. 可以给工厂方法起比较有意义的名字。
  b. 可以控制对象的构造方式和时机。通过cache, singleton等提高性能。
  c. 可以隐藏实现细节。返回任何符合接口的类型。而不是象构造函数一样,只能返回那个类的对象。

3。静态工厂的缺点和不适用的场合也给出来了。

刚才又google了一下别人都对这个问题怎么看。没有看见很有见地的讨论。(多是些大师说好就肯定好之类的话)
这里有一个对Bloch的interview。大家可以看看他的说法:
http://www.artima.com/intv/bloch14.html

还有关于immutability的,也很对我的胃口。(呵呵。谁不是捡自己喜欢的介绍呢?)

http://www.artima.com/intv/bloch11.html


关于不鼓励继承的。可能又会让一些同学不爽。增量编程有什么错?
http://www.artima.com/intv/bloch12.html


至于是否值得,是否实用,怎么用,这是case-by-case的。就象很多其它设计模式一样, 有好处,有适用范围,但是你也不一定就必须使用。
我始终认为,这个决策权应该留给具体类的设计者。使用静态厂不是什么指导思想,它只是一个类的局部设计方法。只要architect不要弄个指导思想说不许用静态厂就行了。
0 请登录后投票
论坛首页 Java企业应用版

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