论坛首页 Java企业应用论坛

对于OCP原则的困惑

浏览 44438 次
该帖已经被评为精华帖
作者 正文
   发表时间: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废话比较多。
0 请登录后投票
   发表时间: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.
0 请登录后投票
   发表时间:2004-10-31  
先不说依赖。两个东西的灵活性是不同的。
用ioc, 我可以同时既有MyClass(new I1()), 也有MyClass(new I2());
无论是用哪个I, MyClass的代码不变。
你MyClass里面用工厂或者jndi的话, 要I1, 就是I1, 如果想换成I2, 就要改动MyClass的代码。更没法同时拥有两个版本。

当然,用setter来注射我也是不喜欢的。我倾向尽量用构造函数(或者工厂函数)注射,这样减少了不必要的中间状态。
0 请登录后投票
   发表时间: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和依赖工厂在某种程度上是一样的,
你从不同角度来看就会发现其实各有利弊。
0 请登录后投票
   发表时间: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容器已经有的前提下讨论它的好处的,那我觉得不公平,
因为既然你想比较,那就应该全面地比较。(注,这一点其实题外话,我觉得不考虑也无所谓,只是随便说说,因为我自己总觉得这里头有不对劲的地方,但是并不肯定是这个)
0 请登录后投票
   发表时间:2004-10-31  
再举个例子,如果XML实现xerces是使用ioc进行配置的,你会觉得它比使用工厂好吗?
如果你使用一个包的时候,总是需要关心具体实现是什么,那么你是不是把一个细节引入乐?
而如果我是那个包的作者,我通过提供一个factory来隐藏内部实现,这样做实在是相对ioc简单明了。

这里又想问下乐,到底大家以为配置文件是属于什么级别的?一个类依赖一个配置文件,算不算依赖?或者说一个类关系依赖配置文件,算不算依赖?如果都认为不算,那我没话好说乐。
0 请登录后投票
   发表时间: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之间的依赖关系)跑。两者没有说谁比谁好,只有适用与不适用,具体采用哪一种,要根据当前的环境来决定。我只是告诉你两者之间最根本的差别,没打算帮你做选择。
0 请登录后投票
   发表时间:2004-11-01  
o,可能这句“你可以把MyClassOne和Apple搬到另一个application里面然后直接投入使用吗?你不能。”让我误会乐。
0 请登录后投票
   发表时间: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里面?
0 请登录后投票
   发表时间: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实现的实例化?
0 请登录后投票
论坛首页 Java企业应用版

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