为什么extends是有害的(二)

原创 2004年07月30日 03:37:00

为什么extends是有害的(二)
2003年8月31日  作者:neo 


[/接上一篇]
有一天,有人也许运行这个代码并且注意到Stack没有运行的如想象的那么快,并且能够在重负荷下使用。你能够重写Stack,以至于它不用ArrayList并且继续提高Stack的效率。这是新的倾向的和有意义的版本:
[/代码]
class Stack
{
     private int stack_pointer = -1;
     private Object[] stack = new Object[1000];

     public void push( Object article )
{
         assert stack_pointer < stack.length;
        
         stack[ ++stack_pointer ] = article;
      }

      public Object pop()
      {
           assert stack_pointer >= 0;
           return stack[ stack_pointer-- ];
       }
   
       public void push_many( Object[] articles )
       {
           assert ( stack_pointer + articles.length ) < stack.length;
       
           System.arraycopy( articles, 0, stack, stack_pointer + 1, articles.length );
           Stack_pointer += articles.length;
       }
}
[/代码]

注意到push_many不再多次调用push()—它做块传输。新的Stack运行正常;事实上,比前一个版本更好。不幸的是,派生类Monitorable_stack不再运行,因为如果push_many()被调用,它不正确的跟踪堆栈的使用(push()的派生类版本不再通过继承的push_many()方法调用,所以push_many()不再更新high_water_mark)。Stack是一个脆弱的类。与关闭它一样,事实上不可能通过小心来消灭这些类型的错误。

注意如果你用接口继承,你就没有这个问题,因为你没有继承对你有害的函数。如果Stack是接口,由Simple_stack和Monitorable_stack实现,那么代码就是更加健壮的。

我提供了一个基于接口的方法在Listing 0.1。这个解决方法和继承实现的方法一样的灵活:你能够用Stack抽象术语来写代码而不必担心你事实上在操作那种具体的堆栈。因为两个实现必须提供公共接口的所有东西,它很难使事情变糟。我仍然有和写基类的代码一样的只写一次,因为我用封装而不是继承。在底层,我不得不通过封装类中的琐碎的访问器方法来访问缺省的实现。(例如,Monitorable_Stack.push(…)(在41行)不得不调用在Simple_stack等价的方法).程序员埋怨写所有这些行,但是写这特别行代码同消除重要的潜在bug是非常小的成本。

[/代码]
Listing 0.1. 用接口消除脆弱基类

   1| import java.util.*;
   2|
   3| interface Stack
   4| {
   5|     void push( Object o );
   6|     Object pop();
   7|     void push_many( Object[] source );
   8| }
   9|
  10| class Simple_stack implements Stack
  11| {   private int stack_pointer = -1;
  12|     private Object[] stack = new Object[1000];
  13|
  14|     public void push( Object o )
  15|     {   assert stack_pointer < stack.length;
  16|
  17|         stack[ ++stack_pointer ] = o;
  18|     }
  19|
  20|     public Object pop()
  21|     {   assert stack_pointer >= 0;
  22|
  23|         return stack[ stack_pointer-- ];
  24|     }
  25|
  26|     public void push_many( Object[] source )
  27|     {   assert (stack_pointer + source.length) < stack.length;
  28|
  29|         System.arraycopy(source,0,stack,stack_pointer+1,source.length);
  30|         stack_pointer += source.length;
  31|     }
  32| }
  33|
  34|
  35| class Monitorable_Stack implements Stack
  36| {
  37|     private int high_water_mark = 0;
  38|     private int current_size;
  39|     Simple_stack stack = new Simple_stack();
  40|
  41|     public void push( Object o )
  42|     {   if( ++current_size > high_water_mark )
  43|             high_water_mark = current_size;
  44|         stack.push(o);
  45|     }
  46|    
  47|     public Object pop()
  48|     {   --current_size;
  49|         return stack.pop();
  50|     }
  51|
  52|     public void push_many( Object[] source )
  53|     {
  54|         if( current_size + source.length > high_water_mark )
  55|             high_water_mark = current_size + source.length;
  56|
  57|         stack.push_many( source );
  58|     }
  59|
  60|     public int maximum_size()
  61|     {   return high_water_mark;
  62|     }
  63| }
  64|
[/代码]

没有提到基于框架编程,那使对于脆弱的基类的讨论是不完整的。诸如Microsoft Foundation Classes(MFC)的基类已经成为建立类库的流行途径。尽管MFC本身正在神圣的隐退,但是MFC的基口已经是根深蒂固,而这无关于Microsoft在那终止,程序员会一直认为Microsoft的方法是最好的方法。

一个基于框架的系统典型的使用半成品的类的构成库开始,这些类不做任何需要做的事,而是依赖于派生类来提供需要的功能。在Java中,一个好的例子就是组件的paint()方法,它是一个有效的占位者;一个派生类必须提供真正的版本。

你能够适度的多国一些东西,但是一个基于定制的派生类的完整的类框架是非常脆弱的。基类是太脆弱了。当我们用MFC编程时,每次Microsoft公布新版本时,我不得不重写我的应用。这些代码将经常编译,但是由于一些基类的改变,它们不能运行。

所有提供的Java包工作的非常好。为了使它们运行,你不需要扩展任何东西。这个已经提供的结构比派生类的框架结构更好。它容易维护和使用,并且如果Sun Microsystems提供的类改变了它的实现,也不会使你的代码处在危险中。

总结脆弱基类
一般,最好避开具体基础类和extends关系,而用接口和implements关系。我的处理规则是,在我的至少80%的代码中完全用接口来完成。例如,我从不用对HashMap的引用;我用对Map接口的引用。(我对interface这个字不是严格的。当你看怎样用接口的时候,InputStream是一个好的接口,尽管它在Java中是作为抽象类来实现的。)

你增加的越抽象,就越灵活。在今天的商业环境下,需求随着程序开发而改变,灵活就是最主要的。而且灵敏编程中的大多数只有代码使用抽象来写才会很好的运行。

如果你近距离的检查四人帮的模式,你将看到这些模式中的很多是提供方法消除实现继承,而最好用接口继承,并且大多数模式的共有特征是用接口继承。这个重要事实是我们开始时提到的:模式是发现而不是发明。模式的出现是当你发现写得很好,易维护的运行代码时。它讲的是这些写得好的,易维护的代码根本的避开了实现继承。

这个文章是从我即将出版的书,暂时命名为《Holub on Patterns:Learning Design Patterns by Looking at Code》,将有Apress在今年秋季出版。

关于作者
Allen Holub从1979年就在计算机工业中工作。当前,他作为一个顾问,在公司的执行、培训和设计编码的服务中提供建议,使公司节省在软件中的成本。它已经出版的了8本书,包括《Taming Java Thread》(Apress, 2000)和《Compiler Design in C》(Pearson Higher Education,1990),并且在加州大学伯克利分校讲课。在他的网站,你会发现更多的信息。(http://www.holub.com

Resources
? A good, though academic, treatment of fragile base classes by Leonid Mikhajlov and Emil Sekerinski:
http://www.cas.mcmaster.ca/~emil/publications/fragile/ecoop98.pdf
? See all of Allen Holub's Java Toolbox columns:
http://www.javaworld.com/columns/jw-toolbox-index.shtml
? View David Geary's Java Design Patterns columns:
http://www.javaworld.com/columns/jw-java-design-patterns-index.shtml
? The Gang of Four book is the seminal work on design patterns: Design Patterns, Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley Publishing Co., 1995; ISBN: 0201633612):
http://www.amazon.com/exec/obidos/ASIN/0201633612/javaworld
? Browse the Design Patterns section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-patterns-index.shtml
? Browse the Object-Oriented Design and Programming section of JavaWorld' Topical Index:
http://www.javaworld.com/channel_content/jw-oop-index.shtml
? Visit the JavaWorld Forum:
http://www.javaworld.com/javaforums/ubbthreads.php?Cat=&C=2
? Sign up for JavaWorld's free weekly newsletters:
http://www.javaworld.com/subscribe

为什么说Kotlin是你下一个要掌握的语言?

-
  • 1970年01月01日 08:00

为什么extends是有害的(一)

概述大多数好的设计者象躲避瘟疫一样来避免使用实现继承(extends 关系)。%80的代码应该完全用interfaces写,不用具体的基类。事实上,四人帮的设计模式的书大量的关于怎样用interfac...
  • passren
  • passren
  • 2004-06-30 13:06:00
  • 863

Java网络编程学习笔记(二)流

输入输出(I/O)输入输出(I/O)是指程序与外部设备或其他计算机进行交互的操作。几乎所有的程序都具有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可...
  • yuan13826915718
  • yuan13826915718
  • 2016-07-24 18:32:47
  • 226

Goto语句还是被认为是有害的吗?

Is goto Still Considered Harmful? By Larry Seltzer, March 11, 2014 Apple's recent security bug...
  • cpq37
  • cpq37
  • 2015-08-07 18:27:47
  • 1806

为什么继承是有害的?

  extends 关键字是很有害的;也许不仅仅是在Char...
  • hean
  • hean
  • 2007-03-25 23:58:00
  • 565

为什么继承是有害的

译注:本文其实已经有人翻译,当时没有具体了解就开始翻译了,如果另一位译者看到这篇文章,希望不要理解为我抄袭的。 extends 关键字是很有害的;也许不仅仅是在Charles Mason的级别上,还...
  • mouyong
  • mouyong
  • 2006-10-20 16:52:00
  • 1039

垃圾分类-特别是有害垃圾

一公斤就是一个积分,现在还有一点奖励,10公斤就可以换一卷垃圾袋,30公斤可以奖励一件雨披,100公斤就可以奖励一个U盘,这样就可以提高业主的积极性。...
  • gnicky
  • gnicky
  • 2014-05-23 18:13:36
  • 1122

为什么Java中继承是有害的一

大多数好的设计者象躲避瘟疫一样来避免使用实现继承(extends 关系)。实际上80%的代码应该完全用interfaces写,而不是通过extends。“JAVA设计模式”一书详细阐述了怎样用接口继承...
  • softwarezhang
  • softwarezhang
  • 2005-04-25 22:42:00
  • 4872

Java网络编程第二章-流

Java网络编程第二章流 概述 Java的输入输出I/O的组织与其他大多数语言都不一样。 I/O基于stream之上,输入流读取数据,输出流写入数据。 还有过滤器(filter),阅读器(rea...
  • qq_26816591
  • qq_26816591
  • 2016-11-06 16:39:01
  • 868

吸烟有害健康flash

  • 2011年12月17日 19:00
  • 637KB
  • 下载
收藏助手
不良信息举报
您举报文章:为什么extends是有害的(二)
举报原因:
原因补充:

(最多只允许输入30个字)