为什么Java中担任大都是有害的
当前位置:以往代写 > JAVA 教程 >为什么Java中担任大都是有害的
2019-06-14

为什么Java中担任大都是有害的

为什么Java中担任大都是有害的

副标题#e#

大大都好的设计者象躲避瘟疫一样来制止利用实现担任(extends 干系)。实际上80%的代码应该完全用interfaces写,而不是通过extends。“Java设计模式”一书具体叙述了奈何用接口担任取代实现担任。这篇文章描写设计者为什么会这么作。

Extends是有害的;也许对付Charles Manson这个级此外不是,可是足够糟糕的它应该在任何大概的时候被避开。“JAVA设计模式”一书花了很大的部门接头用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() );
}

g2()要领此刻可以或许遍历Collection的派生,就像你可以或许从Map中获得的键值对。事实上,你可以或许写iterator,它发生数据,取代遍历一个Collection。你可以或许写iterator,它从测试的框架可能文件中获得信息。这会有庞大的机动性。


#p#副标题#e#

耦合

对付实现担任,一个越发要害的问题是耦合—令人急躁的依赖,就是那种措施的一部门对付另一部门的依赖。全局变量提供经典的例子,证明为什么强耦合会引起贫苦。譬喻,假如你改变全局变量的范例,那么所有用到这个变量的函数也许都被影响,所以所有这些代码都要被查抄,改观和从头测试。并且,所有用到这个变量的函数通过这个变量彼此耦合。也就是,假如一个变量值在难以利用的时候被改变,一个函数也许就不正确的影响了另一个函数的行为。这个问题显著的埋没于多线程的措施。

作为一个设计者,你应该尽力最小化耦合干系。你不能一并消除耦合,因为从一个类的工具到另一个类的工具的要领挪用是一个松耦合的形式。你不行能有一个措施,它没有任何的耦合。然而,你可以或许通过遵守OO法则,最小化必然的耦合(最重要的是,一个工具的实现应该完全埋没于利用他的工具)。譬喻,一个工具的实例变量(不是常量的成员域),应该老是private。我意思是某段时期的,无破例的,不绝的。(你可以或许偶然有效地利用protected要领,可是protected实例变量是可憎的事)同样的原因你应该不消get/set函数—他们对付是一个域公用只是使人感想过于巨大的方法(尽量返回修饰的工具而不是根基范例值的会见函数是在某些环境下是由原因的,那种环境下,返回的工具类是一个在设计时的要害抽象)。

#p#分页标题#e#

这里,我不是墨客气。在我本身的事情中,我发明一个直接的彼此干系在我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[i] );
 }
}

甚至一个象这样简朴的类也有问题。思考当一个用户均衡担任和用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 = 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[i] );
 }
}

到此刻为止,一直都不错,可是思量懦弱的基类问题,我们说你想要在stack建设一个变量, 用它在一段周期内跟踪最大的仓库尺寸。一个大概的实现也许象下面这样:

#p#分页标题#e#

class Monitorable_stack extends Stack
{
 private int high_water_mark = 0;
 private int current_size;
 public void push( Object article )
 {
  if( ++current_size > high_water_mark )
   high_water_mark = current_size;
   super.push( article );
 }
 publish Object pop()
 {
  --current_size;
  return super.pop();
 }
 public int maximum_size_so_far()
 {
  return high_water_mark;
 }
}

这个新类运行的很好,至少是一段时间。不幸的是,这个代码掘客了一个事实,push_many()通过挪用push()来运行。首先,这个细节看起来不是一个坏的选择。它简化了代码,而且你可以或许获得push()的派生类版本,甚至当Monitorable_stack通过Stack的参考来会见的时候,以至于high_water_mark可以或许正确的更新。

    关键字:

在线提交作业