Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API

Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

Why extends is evil

Improve your code by replacing concrete base classes with interfaces

The extends keyword is evil; maybe not at the Charles Manson level, but bad enough that it should be shunned whenever possible. The Gang of Four Design Patterns book discusses at length replacing implementation inheritance (extends) with interface inheritance (implements).

Good designers write most of their code in terms of interfaces, not concrete base classes. This article describes why designers have such odd habits, and also introduces a few interface-based programming basics.

Interfaces versus classes

I once attended a Java user group meeting where James Gosling (Java's inventor) was the featured speaker. During the memorable Q&A session, someone asked him: "If you could do Java over again, what would you change?" "I'd leave out classes," he replied. After the laughter died down, he explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.

Losing flexibility

Why should you avoid implementation inheritance? The first problem is that explicit use of concrete class names locks you into specific implementations, making down-the-line changes unnecessarily difficult.

At the core of the contemporary Agile development methodologies is the concept of parallel design and development. You start programming before you fully specify the program. This technique flies in the face of traditional wisdom—that a design should be complete before programming starts—but many successful projects have proven that you can develop high-quality code more rapidly (and cost effectively) this way than with the traditional pipelined approach. At the core of parallel development, however, is the notion of flexibility. You have to write your code in such a way that you can incorporate newly discovered requirements into the existing code as painlessly as possible.

Rather than implement features you might need, you implement only the features you definitely need, but in a way that accommodates change. If you don't have this flexibility, parallel development simply isn't possible.

Programming to interfaces is at the core of flexible structure. To see why, let's look at what happens when you don't use them. Consider the following code:

f()
{   LinkedList list = new LinkedList();
    //...
    g( list );
}
g( LinkedList list )
{
    list.add( ... );
    g2( list )
}


Now suppose a new requirement for fast lookup has emerged, so the LinkedList isn't working out. You need to replace it with a HashSet. In the existing code, that change is not localized since you must modify not only f() but also g() (which takes a LinkedList argument), and anything g() passes the list to.

Rewriting the code like this:

f()
{   Collection list = new LinkedList();
    //...
    g( list );
}
g( Collection list )
{
    list.add( ... );
    g2( list )
}


makes it possible to change the linked list to a hash table simply by replacing the new LinkedList() with a new HashSet(). That's it. No other changes are necessary.

As another example, compare this code:

f()
{   Collection c = new HashSet();
    //...
    g( c );
}
g( Collection c )
{
    for( Iterator i = c.iterator(); i.hasNext() ;)
        do_something_with( i.next() );
}


to this:

f2()
{   Collection c = new HashSet();
    //...
    g2( c.iterator() );
}
g2( Iterator i )
{   while( i.hasNext() ;)
        do_something_with( i.next() );
}


The g2() method can now traverse Collection derivatives as well as the key and value lists you can get from a Map. In fact, you can write iterators that generate data instead of traversing a collection. You can write iterators that feed information from a test scaffold or a file to the program. There's enormous flexibility here.

Coupling

A more crucial problem with implementation inheritance is coupling—the undesirable reliance of one part of a program on another part. Global variables provide the classic example of why strong coupling causes trouble. If you change the type of the global variable, for example, all functions that use the variable (i.e., are coupled to the variable) might be affected, so all this code must be examined, modified, and retested. Moreover, all functions that use the variable are coupled to each other through the variable. That is, one function might incorrectly affect another function's behavior if a variable's value is changed at an awkward time. This problem is particularly hideous in multithreaded programs.

As a designer, you should strive to minimize coupling relationships. You can't eliminate coupling altogether because a method call from an object of one class to an object of another is a form of loose coupling. You can't have a program without some coupling. Nonetheless, you can minimize coupling considerably by slavishly following OO (object-oriented) precepts (the most important is that the implementation of an object should be completely hidden from the objects that use it). For example, an object's instance variables (member fields that aren't constants), should always be private. Period. No exceptions. Ever. I mean it. (You can occasionally use protected methods effectively, but protected instance variables are an abomination.) You should never use get/set functions for the same reason—they're just overly complicated ways to make a field public (though access functions that return full-blown objects rather than a basic-type value are reasonable in situations where the returned object's class is a key abstraction in the design).

I'm not being pedantic here. I've found a direct correlation in my own work between the strictness of my OO approach, quick code development, and easy code maintenance. Whenever I violate a central OO principle like implementation hiding, I end up rewriting that code (usually because the code is impossible to debug). I don't have time to rewrite programs, so I follow the rules. My concern is entirely practical—I have no interest in purity for the sake of purity.

The fragile base-class problem

Now, let's apply the concept of coupling to inheritance. In an implementation-inheritance system that uses extends, the derived classes are very tightly coupled to the base classes, and this close connection is undesirable. Designers have applied the moniker "the fragile base-class problem" to describe this behavior. Base classes are considered fragile because you can modify a base class in a seemingly safe way, but this new behavior, when inherited by the derived classes, might cause the derived classes to malfunction. You can't tell whether a base-class change is safe simply by examining the base class's methods in isolation; you must look at (and test) all derived classes as well. Moreover, you must check all code that uses both base-class and derived-class objects too, since this code might also be broken by the new behavior. A simple change to a key base class can render an entire program inoperable.

1 | 2 | 3 | 4 |  Next >

Discuss

Start a new discussion or jump into one of the threads below:

Subject Replies Last post
. Why do you ignore Java naming conventions ?
By albert
( Pages 1 2 all )
35 05/17/08 02:40 AM
by Anonymous
. In case of Java, doesn't make sense
By Anonymous
0 05/16/08 06:29 AM
by Anonymous
. Skinning a cat
By Alex B
4 05/16/08 06:29 AM
by Anonymous
. What about GUI's
By Rob
5 05/16/08 06:29 AM
by Anonymous
. extends should be exist
By ray ye
3 05/16/08 06:29 AM
by Anonymous
. Call to synchronized native method unlocks monitor
By Jeral
0 05/16/08 06:28 AM
by Anonymous
. How to avoid get/set in this situation?
By Ralph Cook
13 05/16/08 06:28 AM
by Anonymous
. Funny
By Anonymous
4 05/16/08 06:28 AM
by Anonymous
. Patterns emerge when you look at well-written code
By Anonymous
2 05/16/08 06:28 AM
by Anonymous
. Am I missing something?
By Anonymous
14 05/15/08 06:15 AM
by Anonymous
. You are throwing away Object Orientation
By Jack B.
17 05/09/08 10:43 AM
by Anonymous
. OO is Implementation, NOT Interface, Inheritance!!
By Anonymous
10 05/04/08 05:10 AM
by Anonymous
. What about using reflection on yourself?
By FL JavaMan
0 04/19/08 03:54 AM
by Anonymous
. Pragmatism
By Jonathan
13 04/11/08 03:06 AM
by Anonymous
. Modularity failure?
By Sixtieslibber
1 04/08/08 03:45 PM
by Anonymous
. This kills OOP and that's probably a good thing
By
0 04/05/08 06:05 PM
by Anonymous
. Extends is not evil, it never has been
By Tony Morris
2 04/05/08 06:04 PM
by Anonymous
. Hi, just some question
By HopingToLearn
0 04/04/08 05:42 PM
by Anonymous
. Abstract class
By Anonymous
3 04/02/08 09:22 AM
by Anonymous
. A General Comment on all your "solutions"
By Allen Holub
4 03/31/07 04:52 AM
by Anonymous


Resources