锁定老帖子 主题:对于OCP原则的困惑
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2004-10-30
上面诸位,很多人认为factory在生成A的实例从B到C时是一个改动,
就不能算OCP乐, 难道IOC真的不用改,你不改配置文件能把实例B替换为C? 如果你改配置文件乐,那你这就不叫改? 那我做个使用配置文件的factory好乐,这很容易哦。 大家都说IOC好,呵呵,其实使用IOC它的依赖性才强呢, 比如一个类,它的属性a是由IOC框架注射的,并有一个excute方法需要a属性实例化后才能使用, 这难道不是依赖? 这样的一个类单拿出来使用的话,你是不是需要声明清楚,必须要先setA,然后才能调用这个excute方法? 当然,我不是说这样不好,只不过是,大家考虑问题都那么较真的话,没有理由不对这个较真, 因为怎么说我要是使用factory生成A的话,说明A只是依赖这个factory生成而已,而且它是代码级的约定,基本上不可能会被用错, 但是依赖ioc框架注射生成A的话,它就要依赖外部ioc乐,虽然你这个ioc框架是随时可变的。 当然,之所以选择ioc,最终还是利弊选择的结果,它是非常不错的思想。 下面我说说自己的看法,大家都说是OCP,可是却把这个概念分成2个:对于扩展是开放的,对于更改是封闭的。 我的观点是,这2者是一体的,请不要分开考虑,如果你分开考虑,那么永远是矛盾的。 说回factory模式,上面的例子,有人说,在factory里面修改乐A的实例的生成,从B-->C,这就不是OCP乐,其实不然,请注意, 首先对于client来说,“对于扩展是开放的”这个观点我想大家都是赞同的,无论如何client代码真的没有被修改过, 但是,请注意,为什么说“对于扩展是开放的”? 那是因为“对于更改是封闭的”。 对于什么更改是封闭的?对于A的实例从B变化为C,这个更改是封闭的, 我们只是修改乐factory内部生成A实例的代码,这个时候,这个更改对于外部是不可见的,因此我们说是对更改是封闭的。 对于IOC也是同样的道理,所以它们都是符合OCP的。 (注:其实中间有几页的帖子我没仔细看,因为这里的回复质量还是很高的, 我总是很仔细看,所以有点累乐,因此直接到最后来阐述自己的观点乐。) 回头看乐下,我得观点应该是同 wctang Apache Tomcat 基本一样的。好像me废话比较多。 |
|
返回顶楼 | |
发表时间:2004-10-31
swing 写道 上面诸位,很多人认为factory在生成A的实例从B到C时是一个改动,
就不能算OCP乐, 难道IOC真的不用改,你不改配置文件能把实例B替换为C? 如果你改配置文件乐,那你这就不叫改? 那我做个使用配置文件的factory好乐,这很容易哦。 大家都说IOC好,呵呵,其实使用IOC它的依赖性才强呢, 比如一个类,它的属性a是由IOC框架注射的,并有一个excute方法需要a属性实例化后才能使用, 这难道不是依赖? 这样的一个类单拿出来使用的话,你是不是需要声明清楚,必须要先setA,然后才能调用这个excute方法? 当然,我不是说这样不好,只不过是,大家考虑问题都那么较真的话,没有理由不对这个较真, class MyClassOne { private Apple _apple = AppleFactory.getApple();; public void execute(); { // do something with _apple. } } class MyClassTwo { private Apple _apple; public void setApple(Apple apple); { _apple = apple; } public void execute(); { // do something with _apple. } } 你可以把MyClassOne和Apple搬到另一个application里面然后直接投入使用吗?你不能。你必须把AppleFactory也一起搬过去。That's All. |
|
返回顶楼 | |
发表时间:2004-10-31
先不说依赖。两个东西的灵活性是不同的。
用ioc, 我可以同时既有MyClass(new I1()), 也有MyClass(new I2()); 无论是用哪个I, MyClass的代码不变。 你MyClass里面用工厂或者jndi的话, 要I1, 就是I1, 如果想换成I2, 就要改动MyClass的代码。更没法同时拥有两个版本。 当然,用setter来注射我也是不喜欢的。我倾向尽量用构造函数(或者工厂函数)注射,这样减少了不必要的中间状态。 |
|
返回顶楼 | |
发表时间:2004-10-31
引用 你可以把MyClassOne和Apple搬到另一个application里面然后直接投入使用吗?你不能。你必须把AppleFactory也一起搬过去。That's All.
gigix,你怎么可以这样来较真呢?那我问题你,你把MyClassTwo和Apple搬到另一个application里面,然后能直接投入使用吗?你是选择在另一个application里面引入ioc还是选择在另一个application中在使用这2个类的时候总是先调用setApple方法?还是说你总是假定所有application都是有ioc支持的?即使有你配置文件总要拷贝一下落,这和我带着一个factory有什么区别?老实说,还是带着factory更好。不要以为配置文件配置的就不算代码乐,使用配置文件其实还就是一样的,另外,即使factory我也可以做成可配置的。 当然,我这样说并不认为使用注入就有问题,或者使用MyClassTwo前总是调用setApple方法不好,即使我使用工厂,我仍然是可以设计出MyClassTwo来使用的,并不是只有ioc我才会有MyClassTwo这样的风格,但是有一点你为什么回避?那就是,ioc把一个强制约定(在使用MyClassTwo前,必须调用setApple方法注入有效的Apple实例)提到配置文件中去,你在另一个application中使用你的MyClassTwo时,是不是需要把这个约定带过去的? 而我如果使用工厂,那么我是在代码级做出乐约定,无论如何都需要带着一个factory过去,这有什么不好?这样的强制约定,就好像是强类型约定, 你在其它帖子中曾经说过,你认为java的类型约定还不够强(原话忘乐,但是基本意思是这个),比如C++的泛型就有非常强的类型约定,而为什么要这样? 是因为希望把一些不必要的错误可能提高到编译级,让编译器替我们检查类型错误。 那么我说工厂相对ioc的优势就是一样的,相对ioc来说,工厂也有它的弱点,我有说了,凡事设计总是权衡利弊取其利大者,ioc是不错的选择,因为我们完全可以假定所有项目都有ioc容器。但是这并不能抹煞由于使用ioc带来的代码依赖问题,依赖ioc和依赖工厂在某种程度上是一样的, 你从不同角度来看就会发现其实各有利弊。 |
|
返回顶楼 | |
发表时间:2004-10-31
引用 先不说依赖。两个东西的灵活性是不同的。
用ioc, 我可以同时既有MyClass(new I1()), 也有MyClass(new I2()); 无论是用哪个I, MyClass的代码不变。 你MyClass里面用工厂或者jndi的话, 要I1, 就是I1, 如果想换成I2, 就要改动MyClass的代码。更没法同时拥有两个版本。 你没有看清楚其实,灵活性是一样的,你以为不同? 那么我问你,假设是ioc,当我在A Class中需要一个MyClass(new I1())时,和当我在B CLASS中需要一个MyClass(new I2()),这个变化是封装在哪里的? 不就是在配置文件里吗? 我想问的是,是不是认为放在配置文件中就不算代码乐?你把类关系放到配置文件,这是封装变化的一种方式,但是它并不能说修改配置文件就不算变化乐, 至于说,修改工厂内部代码需要编译,那更可笑,你以为工厂不能实现配置? 况且,工厂需要编译正是其优势所在,我上面已经就这个问题有说明乐不再复述。 其实这里面最大的不同是什么?是已经有现成的ioc容器放在那里等着大家用乐,而工厂却是随需而定,可配置的工厂框架也是一样。 如果现在没有可用的ioc容器,你去比较它和factory模式的实现,你会选择哪个? 如果你说,我们这个讨论就是这样,你们是在ioc容器已经有的前提下讨论它的好处的,那我觉得不公平, 因为既然你想比较,那就应该全面地比较。(注,这一点其实题外话,我觉得不考虑也无所谓,只是随便说说,因为我自己总觉得这里头有不对劲的地方,但是并不肯定是这个) |
|
返回顶楼 | |
发表时间:2004-10-31
再举个例子,如果XML实现xerces是使用ioc进行配置的,你会觉得它比使用工厂好吗?
如果你使用一个包的时候,总是需要关心具体实现是什么,那么你是不是把一个细节引入乐? 而如果我是那个包的作者,我通过提供一个factory来隐藏内部实现,这样做实在是相对ioc简单明了。 这里又想问下乐,到底大家以为配置文件是属于什么级别的?一个类依赖一个配置文件,算不算依赖?或者说一个类关系依赖配置文件,算不算依赖?如果都认为不算,那我没话好说乐。 |
|
返回顶楼 | |
发表时间:2004-11-01
swing 写道 引用 你可以把MyClassOne和Apple搬到另一个application里面然后直接投入使用吗?你不能。你必须把AppleFactory也一起搬过去。That's All.
gigix,你怎么可以这样来较真呢?那我问题你,你把MyClassTwo和Apple搬到另一个application里面,然后能直接投入使用吗?你是选择在另一个application里面引入ioc还是选择在另一个application中在使用这2个类的时候总是先调用setApple方法?还是说你总是假定所有application都是有ioc支持的?即使有你配置文件总要拷贝一下落,这和我带着一个factory有什么区别?老实说,还是带着factory更好。不要以为配置文件配置的就不算代码乐,使用配置文件其实还就是一样的,另外,即使factory我也可以做成可配置的。 你误会了。我的意思是,你有两种选择:带着工厂跑;或者带着配置信息(也就是说,MyClassTwo和Apple之间的依赖关系)跑。两者没有说谁比谁好,只有适用与不适用,具体采用哪一种,要根据当前的环境来决定。我只是告诉你两者之间最根本的差别,没打算帮你做选择。 |
|
返回顶楼 | |
发表时间:2004-11-01
o,可能这句“你可以把MyClassOne和Apple搬到另一个application里面然后直接投入使用吗?你不能。”让我误会乐。
|
|
返回顶楼 | |
发表时间:2004-11-02
引用 那么我问你,假设是ioc,当我在A Class中需要一个MyClass(new I1())时,和当我在B CLASS中需要一个MyClass(new I2()),这个变化是封装在哪里的?
不就是在配置文件里吗? 再强调一遍: ioc和配置文件没关系. 我不需要用配置文件才能成为ioc. 这个变化无论在哪里, 配置文件也好, 客户源代码也好, 它是和我的MyClass代码无关的外部逻辑, 只要MyClass不直接或者间接(通过静态工厂就是间接了)依赖它, 就是ioc了. 对我来说, 客户代码里可以 if(...); return new MyClass(new I1(););; else return new MyClass(new I2(););; MyClass就已经是ioc设计了. 你看看我的一些关于工厂的观点, 就会发现, 我本身不反对工厂. 但是要看用到什么地方. 据我的理解, 你说 class MyClass{ private final I i = IFactory.instance();; ... }和 class MyClass{ private final I i; MyClass(I i);{this.i = i;} ... } 没区别? 是这个意思么? 你说, 如果要从使用I1变成使用I2, 只要改动IFactory的代码, 这和我从new MyClass(new I1())改成new MyClass(new I2())没区别? 那么上面的 if(user_input > 0); return new MyClass(new I1(););; else return new MyClass(new I2(););; 又怎么说? 你怎么通过改动IFactory来做到?你的IFactory里面不是要不只能I1, 要不只能I2? 不管你怎么写, IFactory不是封闭的? 就算你在内部包含了对一万种I实现类的复杂的if-else逻辑处理, 你怎么对付明年才写出来的I1000001类? 而ioc为什么是开放的? 举个例子, 如果我现在要写新的代码, 原有的I1, I2不能满足我的要求, 我只要新写一个I3就是, 根本不用动原来的代码. 这才是open to extension啊. java.lang.io.BufferedReader为什么构造函数里面接受一个Reader? 能否想象自己通过一个ReaderFactory来取得这个Reader? 如果那样, 无论你ReaderFactory内部选择了什么Reader, 我是不是都被绑死在上面了? 如果我自己写了一个MyReader,是否还要反编译jdk的代码, 然后把MyReader放在ReaderFactory里面? |
|
返回顶楼 | |
发表时间:2004-11-02
引用 再强调一遍: ioc和配置文件没关系.
我其实也没有说ioc非要同配置文件有关,只是相对来说,大家对spring的ioc比较熟悉,所以就说起配置文件乐, 我的核心意思是,无论你用什么方法,你总有一个地方去存放你的规则, 比如: 引用 对我来说, 客户代码里可以 java代码: 1 if(...) 2 return new MyClass(new I1()); 3 else return new MyClass(new I2()); MyClass就已经是ioc设计了. 你这么说,你是把你的规则放到客户代码里乐, 那么接着你自己的话: 引用 而ioc为什么是开放的? 举个例子, 如果我现在要写新的代码, 原有的I1, I2不能满足我的要求, 我只要新写一个I3就是, 根本不用动原来的代码. 这才是open to extension啊.
请你仔细读自己的话,是不是矛盾的?!! 当你新写一个I3的时候,你需不需要修改你的客户代码以便支持你新写的I3的? 好了,如果你说,我这是在新的application里面一个新应用,你有新的客户端代码比如: return new MyClass(new I3()); 好嘛,你以为我就不能新写一个工厂来支持新的I3乐?!!! 其实对我来说,你写的: 引用 1 if(...) 2 return new MyClass(new I1()); 3 else return new MyClass(new I2()); 我就是放在我的工厂里面的代码,你说说看有没有区别? 而且比你现在这样还好些,怎么说我要是增加乐I3,也只是修改工厂这一个地方而已,而你的代码如果(注意,是如果)散落在客户端各个地方,到时候还怎么改?好了,如果你的客户端只有一个地方有你上面那段代码,而且你是把它放在一个方法里面的,呵呵,你这就不算工厂乐? 我说你这是准工厂,相对工厂来说,它依赖实现细节,违反DIP原则,你还称之为ioc。 另外, 引用 据我的理解, 你说 java代码: 1 class MyClass{ ...} 2 private final I i = IFactory.instance(); 3 ... 4 } 和 java代码: 1 class MyClass{ ...} 2 private final I i; 3 MyClass(I i){this.i = i;} 4 ... 5 } 没区别? 是这个意思么? 我不是说没有区别,区别当然是有的, 目的上也会有不同, 只是说解决问题的方法上它们是基本相同的, 都是依靠抽象来解耦。 一个把依赖关系建立在工厂,一个把依赖关系建立在外部, 其实你说的时候总是把MyClass孤立起来看,它一个放在那里当然会显的独立乐, 但是,如果你所有代码都是依靠这个方式的,那么我请问,你到底在那里new 的I的实现?你以为你解耦乐,那是因为你从局部看这个问题。 现在我们假设XML的解析器实现没有工厂,按照你的逻辑我们有如下代码: MyClass(Document doc) { } 好了,MyClass是够独立乐,它依赖一个接口,可惜,我们把所有工厂都去掉乐,这个时候你改如何去实例化一个Document的实例呢? 如果你自己去new,那么你还不是一样,只是把一个依赖关系向上传递乐一层,总之,你是要想办法去实例化一个Document的,总不可能变出来。 对于: 引用 不管你怎么写, IFactory不是封闭的? 就算你在内部包含了对一万种I实现类的复杂的if-else逻辑处理, 你怎么对付明年才写出来的I1000001类? 你就不要较真乐,方法有很多中,只是你选择乐if-else而已,我才不会这样。 而 引用 你看看我的一些关于工厂的观点, 就会发现, 我本身不反对工厂. 但是要看用到什么地方.
这个观点我是和你一样的,从头到尾我甚至没有说我喜欢使用工厂。 至于你的例子: 引用 java.lang.io.BufferedReader为什么构造函数里面接受一个Reader? 能否想象自己通过一个ReaderFactory来取得这个Reader?
如果那样, 无论你ReaderFactory内部选择了什么Reader, 我是不是都被绑死在上面了? 如果我自己写了一个MyReader,是否还要反编译jdk的代码, 然后把MyReader放在ReaderFactory里面? 这是一个非常好的实例,下面我说的有点较真,所以我粗略说下我的看法,你不要非同我争就好乐,因为我不否认这些设计本身。 我的问题是,当你去实现一个MyReader时,你是如何去实例化的,这是时候你是不是总是使用new MyReader的?还是说,你会在情况变复杂的时候,使用一个工厂之类的东西去封装掉你自己的Reader实现的实例化? |
|
返回顶楼 | |