Maximize flexibility with interfaces and abstract classes

Design with interfaces and abstract classes to satisfy both type and implementation issues

RELATED TOPICS

Though type is an extremely important object-oriented concept, it is often overlooked in favor of implementation-centric concerns. Java program development is as much about design as about implementation. I don't see much advantage in totally separating the two. Good developers simultaneously consider design and implementation issues at all times during development, and program design decisions often revolve around the system's type structure. Ignoring or de-emphasizing a system's type-centric views tends to confuse that system's design.

Introductory discussions of the difference between interfaces and abstract classes exemplify the implementation-centric view adopted by most Java texts. Such discussions explain how to use either an interface or an abstract class, but seldom explain why you would choose to use one or the other. Through a simple example, this article investigates the design decisions driving the use of interfaces and abstract classes and shows why interfaces satisfy type concerns and abstract classes satisfy implementation concerns.

Groundwork

To lay the groundwork necessary for a clear discussion, I review Java class and interface constructs from both type and implementation-centric viewpoints. As detailed in my earlier article, "Thanks Type and Gentle Class" (JavaWorld, January 2001), classes and interfaces establish a system's type hierarchy, whereas only classes establish the implementation hierarchy. Classes divide into two types: concrete and abstract.

I'll briefly review three constructs: concrete classes, abstract classes, and interfaces. I start with the concrete class.

Concrete class

In Java, because concrete classes do not require a special designation during declaration, they are typically just called classes. Each declared class performs double duty. From a type perspective, the class defines a type and a set of operations for the class's object instances. From an implementation perspective, the class provides implementation code for each declared operation. These implementation modules are the class methods.

Classes inherit operations from supertypes and methods from superclasses. The restriction that each class extend only one direct superclass means the implementation hierarchy follows a single inheritance policy. Since a class can implement one or more interfaces and extend one other class, type operations can be inherited from multiple supertypes. Thus, Java supports multiple inheritance in the type hierarchy.

Table 1. Concrete class
Concrete class
Type-centricImplementation-centric
Defines a type and a set of operations on that typeProvides implementation for each declared type operation

Abstract class

The abstract keyword in the class declaration identifies an abstract class. From a type perspective, the type-defining characteristics of an abstract class match those of a concrete class. The two differ in that an abstract class optionally provides implementation code for each declared operation. An abstract class can define an abstract method (that is, define a type operation without method implementation) by using the abstract keyword in the method declaration.

An abstract class does not need any abstract methods; however, any class with an abstract method must be declared abstract. An important characteristic of abstract classes is that they cannot be used to instantiate objects. A runtime object must be capable of responding to any legal method call, the legality of which is determined at compile-time and governed by the system's declared type structure. An object instantiated from an abstract class could receive a valid method call, but have no implementation code for the specified operation. Therefore, all objects must instantiate from concrete classes to guarantee the availability of runtime implementation code for each permissible object method call.

Table 2. Abstract class
Abstract class
Type-centricImplementation-centric
Defines a type and a set of operations on that typeOptionally provides implementation for declared type operations

Interface

In Java, an interface defines a type and a set of operations on that type, just like a class. From a type perspective, an interface's type-defining characteristics match those of concrete and abstract classes. Interfaces, however, do not permit any method implementations, so from an implementation perspective, interfaces guarantee no implementation. That is key to enabling multiple type inheritance while guaranteeing single implementation inheritance.

Table 3. Interface
Interface
Type-centricImplementation-centric
Defines a type and a set of operations on that typeNo implementation

As indicated in the above three tables, from a type perspective, concrete classes, abstract classes, and interfaces are identical constructs. Each defines a type and the set of permissible operations on that type. From an implementation perspective, the three constructs provide a full range of method implementation possibilities. Concrete classes guarantee that all type operations have method implementations, abstract classes permit type operations with optional method implementations, and interfaces guarantee type operations with no method implementations.

Since the three constructs have identical type-defining characteristics, you might surmise that type doesn't play a role in choosing between them. However, don't forget about the all-important matter of inheritance. Restricted to single implementation inheritance, the class construct cannot combine types defined by multiple classes in distinct implementation hierarchies, which significantly influences designing systems for polymorphic behavior.

Implementation reuse

To bring pragmatism to the previous section's borderline theoretic discussion, consider the development of classes representing possible geometries for dealing with positions defined by the Global Positioning System (GPS). Depending on specific problem domain needs, you can use various geometries for such tasks as calculating distance and direction between GPS positions. Two commonly used geometries are plane geometry, which models the earth's surface using local Cartesian coordinates, and spherical geometry, which models the earth's surface as a sphere. Figure 1 shows a simple UML class diagram for these two geometries.

Figure 1. Plane and spherical geometry classes

The detailed algorithms used in these classes stretch beyond this article's scope. Our discussion pertains to the implementation and type hierarchy design decisions that best utilize the classes. Given this common scenario of two or more specializations of a single generality, object-oriented design experience suggests looking for ways to structure the system. The question is, based on what criteria?

Developers are typically taught to first look for ways to reuse implementation code. Given this criteria, common implementations in the PlaneGeometry and SphericalGeometry classes factor into a superclass. Applying this technique yields the class diagram in Figure 2.

Figure 2. Common method implementations factored into an abstract class

The two geometry classes feature identical implementations for two methods:

  1. heading(Position), which determines the direction from each class's contained Position (class not shown) to a specified Position
  2. within(Position,double), which determines if the contained Position is within a circle defined by the specified center Position and double radius

These two methods, in turn, utilize geometry-specific implementations of the methods course(Position) and radians(Position), which are, therefore, declared as abstract methods. A rudimentary outline of class Geometry looks like the following:

//
// Geometry.java
//
package gps;
public abstract class Geometry
{
  abstract public double radians( Position destination );
  abstract public double course( Position destination );
  
  public boolean within( Position center, double radians )
  {
    // Implementation calls radians(Position).
  }
  public double heading( Position destination )
  {
    // Implementation calls course(Position).
  }
  protected Position position;
}

Note that although PlaneGeometry and SphericalGeometry each declare a within(Position,Position) method to determine if the contained Position is within a box defined by the specified Position objects, this operation is not factored into the abstract class Geometry. Certainly it could be, and later will be, but doing so now rejects the current structuring criteria of achieving implementation code reuse. Because no common implementation is available, the operation is not included in the abstract class Geometry. From an implementation-centric viewpoint, the only abstract methods necessary in Geometry are those used by concrete methods within that class.

RELATED TOPICS
1 2 Page
View Comments
Join the discussion
Be the first to comment on this article. Our Commenting Policies