Why extends is evil

Improve your code by replacing concrete base classes with interfaces

Page 2 of 2

I provide an interface-based solution in Listing 0.1. This solution has the same flexibility as the implementation-inheritance solution: you can write your code in terms of the Stack abstraction without worrying about what kind of concrete stack you actually manipulate. Since the two implementations must provide versions of everything in the public interface, it's much more difficult to get things wrong. I still have the benefit of writing the equivalent of base-class code only once, because I use encapsulation rather than derivation. On the down side, I have to access the default implementation through a trivial accessor method in the encapsulating class. (Monitorable_Stack.push(...) (on line 41) has to call the equivalent method in Simple_stack, for example.) Programmers grumble about writing all these one-liners, but writing an extra line of code is a trivial price to pay for eliminating a significant potential bug.

Listing 0.1. Eliminate fragile base classes using interfaces

   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| 

Frameworks

A discussion of fragile base classes would be incomplete without a mention of framework-based programming. Frameworks such as Microsoft Foundation Classes (MFC) have become a popular way of building class libraries. Though MFC itself is blessedly fading away, MFC's structure has been ingrained in countless Microsoft shops where programmers assumed that the Microsoft way was the best way.

A framework-based system typically starts with a library of half-baked classes that don't do everything they need to do, but rather rely on a derived class to provide missing functionality. A good example in Java is the Component's paint() method, which is effectively a place holder; a derived class must provide the real version.

You can get away with this sort of thing in moderation, but an entire class framework that depends on derivation-based customization is brittle in the extreme. The base classes are too fragile. When I programmed in MFC, I had to rewrite all my applications every time Microsoft released a new version. The code would often compile, but then not work because some base-class method changed.

All Java packages work quite well out of the box. You don't need to extend anything to make them function. This works-out-of-the-box structure is better than a derivation-based framework. It's easier to maintain and use, and doesn't put your code at risk if a Sun Microsystems-supplied class changes its implementation.

Summing up fragile base classes

In general, it's best to avoid concrete base classes and extends relationships in favor of interfaces and implements relationships. My rule of thumb is that 80 percent of my code at minimum should be written entirely in terms of interfaces. I never use references to a HashMap, for example; I use references to the Map interface. (I use the word "interface" loosely here. An InputStream is effectively an interface when you look at how it's used, even though it's implemented as an abstract class in Java.)

The more abstraction you add, the greater the flexibility. In today's business environment, where requirements regularly change as the program develops, this flexibility is essential. Moreover, most of the Agile development methodologies (such as Crystal and extreme programming) simply won't work unless the code is written in the abstract.

If you examine the Gang of Four patterns closely, you'll see that many of them provide ways to eliminate implementation inheritance in favor of interface inheritance, and that's a common characteristic of most patterns. The significant fact is the one we started with: patterns are discovered, not invented. Patterns emerge when you look at well-written, easily maintainable working code. It's telling that so much of this well-written, easily maintainable code avoids implementation inheritance at all cost.

This article is adapted from my forthcoming book, tentatively titled Holub on Patterns: Learning Design Patterns by Looking at Code, to be published by Apress (www.apress.com) this fall.

Allen Holub has worked in the computer industry since 1979. He currently works as a consultant, helping companies not squander money on software by providing advice to executives, training, and design-and-programming services. He's authored eight books, including Taming Java Threads (Apress, 2000) and Compiler Design in C (Pearson Higher Education, 1990), and teaches regularly for the University of California Berkeley Extension. Find more information on his Website (http://www.holub.com).

Learn more about this topic

| 1 2 Page 2