http://firebody.blogbus.com/logs/2004/08/338549.html
转自:javaeye论坛 作者:potian
说明:这些帖子都是在讨论工厂模式,容器配置,构造函数产生对象的方式各自优缺时的回帖。很不错!感谢potian!!!
除了静态方法对具体子类的直接依赖问题之外,对象的产生封装在对象内部也是一个很奇怪的想法,如果在不同情况下,例如需要同步或者需要lazy等等的话,你必须针对修改你的代码,实际上我们最关心的是能够在不同场合使用同一个对象的业务逻辑,而你这样做的话会紧紧因为我们需要不同的对象创建方法而修改对象的代码,这是非常不智的,这也是singleton被视作evil的重要原因之一。(例如singleton,有时候我们希望使用超类的实例,有时候希望使用子类的实例,有的时候我们希望产生单个,有的时候需要产生多个[取singleton单控制点的含义],有的时候需要同步,有的时候不需要同步,在集群的情况我们甚至可能需要数据库来实现唯一化控制,有时候希望缓存,有的时候不需要缓存,有的时候希望增强,有的时候希望采用动态代理产生。所以一般来说,我们希望在一个系统内部最多只有一个入口的singleton,而我们也往往也不打算在其它不同的场合重用这个singleton)。
代码的重用是对他业务逻辑的重用,这正是对象的核心价值。所以如同charon所说的,相对而言,我们往往不在乎组装代码的重用性,而是追求业务代码本身可以被按照不同的组装方式使用,例如既可以在EJB下,也可以在普通的Java应用程序中,既可以作为远程传输的对象,因此,我们往往不会去强制对象构造和产生的方式(例如由EJB容器产生,由IOC容器产生、有抽象工厂产生,或者直接由new产生),相反,是把对象的产生交给外部负责,这样才能达到在不同场合下对象最大的可重用性。说个简单的,如果对象的构造方法是私有的,那么现在的很多框架(例如hibernate,JavaBean,EJB等等你就根本不能使用了).这也是现在认为POJO比有特殊要求的对象更好的原因,因为任何一种框架和技术都可以自由选择自己的方式来创建对象。
所有创建型设计模式和IoC容器的使用正是基于这样的假设的,只有对象把构造的责任交给外部来实现,那么我们才能有效地隐藏对象创建的时机、方式、方法,给不修改对象本身的代码而能够使用不同的对象创建方式(包括使用子类,替代类)提供了前提,从而提高对象实现业务逻辑的在不同场合的可重用性。所以不是说一定要用IOC容器或者用抽象工厂,而是可以用这些方法,也可以不用这种方法。而就抽象工厂和IoC容器本身而言,通过它们各自的封装,可以进一步实现对不同具体实现子类的解绑,就更加好了。如果有一天你不想用Pico或者不想用抽象工厂,你可以选择其他更加合适的方法来实现解绑。而这个责任不应该交给对象自己来实现,因为我们根本无法预料这个对象将会以什么样的方式被构造出来,在什么时候构造出来,需要依赖什么其它外部机制构造出来(例如可能依赖数据库,或者依赖串行化),这最好是有外部使用这个对象的环境来决定
最终,我们希望能够让对象的创建、对象的销毁完全脱离对象的使用,垃圾收集器已经为我们提供VM级别的支持,而抽象工厂和工厂方法以及其它构造型设计模式,IoC从模式和框架的角度给我们另一半。
你这个已经是每次返回一个新的对象了,我要每次得到同一对象,我该怎么办,你是不是叫我重新再在上面包一个类,把第一次取到的对象缓存起来,以后每次去取那个对象?这个时候你每次new一个还有什么意义,你说可以把它改成单个,那我原先在用的代码怎么办?
再复杂一点,如果是需要在集群的服务器之间保持singleton,有的时候我需要用数据库来持久对象状态,通过数据库来保持唯一性,那你这份代码需要依赖于JDBC,这个时候如果一个普通的应用程序是不是也要依赖于JDBC?
区别在于你的代码是放在本类里面的,你这个类构造会直接依赖于你的环境和当前的假设,别人根本没办法重用你的类
构造函数为什么没有这些缺陷,因为我们可以通过另外一个类来实现和不同环境的结合,而我们正在谈论的这个类本身可以在任何地方使用而不需要修改任何代码.这是非常重要的,这是重用的基础,从我们谈的范围内来说,如果对照OCP,那么它是C,一旦关闭,永不修改。构造函数为什么没有这个问题,因为他自己不对任何构造自己的方法作出额外的假设(构造函数是最最近基本的假设了),而把构造的方法、意图和时机完全交给了外部,如果对照OCP,那么它是O。除非你的静态方法永远等同于new,不然的话,你任何一种实现方式都是对可能的构造方式进行无意义的假设,会限制其他场合对你这份代码重用的可能性,而如果永远等于new,那你这个静态方法还有什么用。
举例来说,假设有3个应用,对一个对象需要三种不同的构造方法,一个需要proxy以实现拦截,一个需要普通的java类做测试,另一个需要从数据库里面读取以保持集群之间的唯一性,我只需要对不同的环境写不同的工厂,而不需要去改你那个嵌在业务代码中的静态方法,即使改了也不能同时重用于三个场合,你这个时候告诉我这是外部设计的决策. 和你的实现细节无关,我可以在外面去包一个类,那我要你那个静态方法干什么,这个静态方法里面到底是返回proxy还是普通java对象还是从数据库里面读取,任何一个都没有意义
总而言之,任何在本类内部假设自己将被如何、何时、以何种方式进行构造的代码是极其不利于重用的(例如singleton就是一种限制重用的方法,只不过它由他自己适合的环境,因为任何系统里面肯定有一些类是可以重用,而有一些是特定于某个应用的)。因为你适合了一种环境的构造,必然会产生对另一种状况的不适合,所以最好的方式是什么也不假设,把如何构造的责任传递给另外一个类或者框架,那个类可以结合具体的重用场合实现构造,缓存,单件化,动态代理等等它希望做的任何动作。
面向对象的中心点是职责分离和变化频率的分离,对一个希望被重用的对象来说,它的业务代码是它变化频率较低的部分(这是重用的基本假设),而由于目前各种不用容器、测试、分布式计算、事务处理等等场合的需要,它如何被构造的可能性则是一个比它本身业务逻辑变化率高得多的东西,这两种职责必须被分离。
至于你这种设计方法完全依赖于子类和继承上的困难就更不用谈了。你说你的大多数类不让继承,OO最重要的概念就是差异编程和增量编程,这是提高内外部质量、提高生产率的核心思想,如果没有记错的话,《面向对象软件构造》第一章里面就明确地提出了几个重要的内外部指标,也是整个OO思想的软件工程基础。