会计考友 发表于 2012-8-4 12:28:23

JAVA技巧:java中extends的危害(1)

概述
  年夜年夜都好的设计者象遁藏瘟疫一样来避免使用实现担任(extends 关系)。€的代码应该完全用interfaces写,不器具体的基类。事实上,四人帮的设计模式的书年夜量的关于若何用interface担任庖代实现担任。这个文章描述设计者为什媚暌剐这样的怪癖的设法。Extends是有害的;也许对于Charles Manson这个级此外不是,可是足够糟糕的它应该在任何的可能的时辰被避开。四人帮的设计模式花了很年夜的部门谈判用interface担任庖代实现担任。好的设计者在他的代滤鱿脯年夜部门用interface,而不是具体的基类。这个文章谈判,为什么设计者会有这样怪癖的习惯,而且也介绍一些基于interface的编程基本。Interface和Class
  一次,我加入一个Java用户组的会议。在会议中,Jams Gosling(Java之父)做倡议人讲话。在那令人难忘的Q&A部门,有人问他:“如不美观你年夜头机关Java,你想改变什么?”。“我想丢弃classes”他回覆。在笑声平息后,它诠释说,真正的问题不是因为class自己,而是实现担任(extends 关系)。接口担任(implements关系)是更好的。你应该尽可能的避免实现担任。失踪去了矫捷性
  为什么你应该避免实现担任呢?第一个问题是明晰的利器具体类名将你固定到特定的实现,给底层的改变增添了不需要的坚苦。在当前的火速编程体例中,焦点是并行的设计和开发的概念。在你具体设计轨范前,你起头编程。这个手艺分歧于传统体例的形式----传统的体例是设计应该在编码起头前完成----可是良多成功的项目已经证实你能够更快速的开发高质量代码,相对于传统的按部就班的体例。可是在并行开发的焦点是主张矫捷性。你不得不以某一种体例写你的代码以至于最新发现的需求能够尽可能没有疾苦的合并到已有的代码中。胜于实现你也许需要的特征,你只需实现你明晰需要的特征,而且适度的对转变的包容。如不美观你没有这种矫捷,并行的开发,那简直不成能。对于Inteface的编程是矫捷结构的焦点。为了声名为什么,让我们看一下当使用它们的时辰,会发生什么。考虑下面的代码:
  [/代码]
  f()
  { LinkedList list = new LinkedList();
  //...
  g( list );
  }g( LinkedList list )
  {
  list.add( ... );
  g2( list )
  }
  [/代码]
  此刻,假设一个对于快速发芽的需求被提出,以至于这个LinkedList不能够解决。你需要用HashSet来庖代它。在已有代滤鱿脯转变不能够局部化,因为你不仅仅需要改削f()也需要改削g()(它带有LinkedList参数),而且还有g()把列表传递给的任何代码。
  象下面这样重写代码:
  [/代码]
  f()
  { Collection list = new LinkedList();
  //...
  g( list );
  }g( Collection list )
  {
  list.add( ... );
  g2( list )
  }
  [/代码]
  这样改削Linked list成hash,可能只是简单的用new HashSet()庖代new LinkedList()。就这样。没有其他的需要改削的处所。
  作为另一个例子,斗劲下面两段代码:
  [/代码]
  f()
  { Collection c = new HashSet();
  //...
  g( c );
  }g( Collection c )
  {
  for( Iterator i = c.iterator(); i.hasNext() )
  do_something_with( i.next() );
  }
  [/代码]
  和
  [/代码]
  f2()
  { Collection c = new HashSet();
  //...
  g2( c.iterator() );
  }g2( Iterator i )
  { while( i.hasNext() )
  do_something_with( i.next() );
  }
 

会计考友 发表于 2012-8-4 12:28:24

JAVA技巧:java中extends的危害(1)

 [/代码]
  g2()体例此刻能够遍历Collection的派生,就像你能够年夜Map中获得的键值对。事实上,你能够写iterator,它发生数据,庖代遍历一个Collection。你能够写iterator,它年夜测试的框架或者文件中获得信息。这会有巨年夜的矫捷性。
  耦合
  对于实现担任,一个加倍关头的问题是耦合---令人焦躁的依靠,就是那种轨范的一部门对于另一部门的依靠。全局变量供给经典的例子,证实为什么强耦合会引起麻烦。例如,如不美观你改变全局变量的类型,那么所有用到这个变量的函数也许都被影响,所以所有这些代码都要被搜检,变换和年夜头测试。而且,所有用到这个变量的函数经由过程这个变量彼此耦合。也就是,如不美观一个变量质ё仝难以使用的时辰被改变,一个函数也许就不正确的影响了另一个函数的行为。这个问题显著的潜匿于多线程的轨范。作为一个设计者,你应该全力最小化耦合关系。你不能一并消弭耦合,因为年夜一个类的对象到另一个类的对象的体例挪用是一个松耦合的形式。你不成能有一个轨范,它没有任何的耦合。然而,你能够经由过程遵守OO轨则,最小化必然的耦合(最主要的是,一个对象的实现应该完全潜匿于使用他的对象)。例如,一个对象的实例变量(不是常量的成员域),应该老是private。我意思是某段时代的,无破例的,不竭的。(你能够偶然有用地使用protected体例,可是protected实例变量是可憎的事)同样的原因你应该不用get/set函数---他们对于是一个域公用只是使人感应过于复杂的体例(尽管返回润色的对象而不是根基类型值的访谒函数是在某些情形下是由原因的,那种情形下,返回的对象类是一个在设计时的关头抽象)。这里,我不是骚人气。在我自己的工作中,我发现一个直接的彼此关鲜ё仝我OO体例的严酷之间,快速代码开发和轻易的代码实现。无论什么时辰我违反中心的OO原则,如实现潜匿,我结不美观重写阿谁代码(一般因为代码是不成调试的)。我没有时刻重写代码,所以我遵循那些轨则。我关心的完全适用—我对清洁的原因没有乐趣。懦弱的基类问题
  此刻,让我们应悠揭捉合的概念到担任。在一个用extends的担任实现系统中,派生类长短常慎密的和基类耦合,当且这种慎密的毗连是不期望的。设计者已经应用了绰号“懦弱的基类问题”去描述这个行为。基本类被认为是懦弱的是,因为你在看起来平安的情形下改削基类,可是昔时夜派生类担任时,新的行为也许引起派生类呈现功能杂乱。你不能经由过程简单的在隔离下搜检基类的体例来分辩基类的转变是平安的;而是你也必需看(和测试)所有派生类。而且,你必需搜检所有的代码,它们也用在基类和派生类对象中,因为这个代码也许被新的行为所打破。一个对于基本类的简单转变可能导致整个轨范不成操作。让我们一路搜检懦弱的基类和基类耦盒罾υ题。下面的类extends了Java的ArrayList类去使泥像一个stack来运转:
  [/代码]
  class Stack extends ArrayList   { private int stack_pointer = 0;public void push( Object article )
  { add( stack_pointer++, article );
  }public Object pop()
  { return remove( --stack_pointer );
  }public void push_many( Object[] articles )
  { for( int i = 0; i < articles.length; ++i )
  push( articles );
  }
  }
  [/代码]甚至一个象如惺招的类也有问题。思虑当一个用户平衡担任和用ArrayList的clear()体例去弹出仓库时:
  [/代码]
  Stack a_stack = new Stack();
  a_stack.push("1");
  a_stack.push("2");
  a_stack.clear();
  [/代码]这个代码成功编译,可是因为基类不知道关于stack指针仓库的情形,这个stack对象当前在一个不决义的状况。下矣闽对于push()挪用把新的项放入索引2的位置。(stack_pointer的当前值),所以stack有用地有三个元素-下边两个是垃圾。(Java的stack类恰是有这个问题,不要用它).对这个令人厌恶的担任的体例问题的解决法子是为Stack笼盖所有的ArrayList体例,那能够改削数组的状况,所以笼盖正确的操作Stack指针或者抛出一个破例。(removeRange()体例对于抛出一个破例矣闽好的候选体例)。这个体例有两个错误谬误。第一,如不美观你笼盖了所有的工具,这个基类应该真正的是一个interface,而不是一个class。如不美观你不用任何担任体例,在实现担任中就没有这一点。第二,更主要的是,你不能够让一个stack撑持所有的ArrayList体例。例如,令人懊恼的removeRange()没有什么浸染。独一实现无用体例的合理的路子是使它抛出一个破例,因为它应该永远不被挪用。这个体例有用的把编译错误成为运行错误。欠好的体例是,如不美观体例只是不被界说,编译器会输出一个体例未找到的错误。如不美观体例存在,可是抛出一个破例,你只有在轨范真正的运行时,你才能够发现挪用错误。对于这个基类问题的一个更好的解决法子是封装数据结构庖代用担任。这是新的和改良的Stack的版本:
  [/代码]
  class Stack
  {
  private int stack_pointer = 0;
  private ArrayList the_data = http://www.qnr.cn/pc/java/xinde/201007/new ArrayList();public void push( Object article )
  {
  the_data.add( stack_poniter++, article );
  }public Object pop()
  {
  return the_data.remove( --stack_pointer );
  }public void push_many( Object[] articles )
  {
  for( int i = 0; i < o.length; ++i )
  push( articles );
  }
  }
页: [1]
查看完整版本: JAVA技巧:java中extends的危害(1)