April 2, 2020
Hot Topics:

Designing with Interfaces & Abstract Classes

  • By Matt Weisfeld
  • Send Email »
  • More Articles »

This series, The Object-Oriented Thought Process, is intended for someone just learning an object-oriented language and who wants to understand the basic concepts before jumping into the code, or someone who wants to understand the infrastructure behind an object-oriented language he or she is already using. These concepts are part of the foundation that any programmer will need to make the paradigm shift from procedural programming to object-oriented programming.

Click here to start at the beginning of the series.

In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems web site http://java.sun.com/. You can download the standard edition, J2SE 5.0, at http://java.sun.com/j2se/1.5.0/download.jsp to compile and execute these applications. I often reference the Java J2SE 5.0 API documentation and I recommend that you explore the Java API further. Code listings are provided for all examples in this article as well as figures and output (when appropriate). See the first article in this series for detailed descriptions for compiling and running all the code examples.

In the previous column, you started your exploration of interfaces. In this installment, you delve deeper in interfaces and explain how they relate to abstract classes.

Implementing Contracts

Since the advent of computer software, the concept of reusing code has been reinvented many times. The Object-Oriented (O-O) paradigm is no different. One of the major advantages touted by O-O proponents is that you can write code once, and then reuse it to your heart's content.

This is true to a certain degree. As with all design approaches, the utility and the reusability of code depend on how well it was designed and implemented. Object-Oriented development does not hold the patent on code reuse. There is nothing stopping anyone from writing very robust and reusable code in a non-O-O language. Certainly, there are countless numbers of routines and functions, written in structured languages such as COBOL and C, which are of high quality and quite reusable.

Thus, it is clear that following the O-O paradigm is not the only way to take advantage of reuse. Yet, the O-O approach does provide several mechanisms for facilitating, and even enforcing, the development of reusable code. One way to accomplish this goal is to create something I refer to as programming contracts. In this article, you focus on using interfaces and abstract classes to create contracts and encourage reusable code.

What Is a Contract?

In the previous article, you learned about the concept of a design contract. In the context of this article, you will consider a contract to be any mechanism that requires a developer to comply with the specifications of an Application Programming Interface (API). Often, an API is referred to as a framework. The online dictionary Dictionary.com (www.dictionary.com) defines a contact as:

  1. An agreement between two or more parties for the doing or not doing of something specified.
  2. An agreement enforceable by law.

This is exactly what happens when a developer uses an API—with the project manager or business owner representing the law. In short, when using contracts, the developer is required to comply with the rules defined in the framework. This includes issues such as method names, number of parameters, and so forth. In short, standards are created to facilitate good coding practices.

The term contract is used widely in many aspects of business, including software development. Do not confuse the concept here with other possible software design concepts called contracts.

Enforcement is vital because it is always possible for a developer to break a contract. Without enforcement, a rogue developer could decide to re-invent the wheel and write his or her own code rather than use the code provided by the framework. There is little benefit to a standard if people routinely disregard or circumvent it. In Java and the .NET languages, the two ways to implement contracts are to use abstract classes and interfaces.

For this article, I used C# as the .NET representative. However, these concepts apply to all .NET languages, such as Visual Basic .NET.

Abstract Classes

One way a contract is implemented is via an abstract class. An abstract class is a class that contains one or more methods that do not have any implementation provided. Suppose that you have an abstract class called Shape. It is abstract because you cannot instantiate it. If you ask someone to draw a shape, the first thing they will most likely ask you is "What kind of shape?" Thus, the concept of a shape is abstract. However, if someone asks you to draw a circle, this does not pose quite the same problem because a circle is a concrete concept. You know what a circle looks like. You also know how to draw other shapes, such as rectangles.

How does this apply to a programming contract? Assume that you want to create an application to draw shapes. Your goal is to draw every kind of shape represented in your proposed design, as well as ones that may be added later. There are two conditions you must adhere to.

First, you want all shapes to use the same syntax to draw themselves. For example, you want every shape implemented in your system to contain a method called draw (). Thus, seasoned developers implicitly know that to draw a shape you simply invoke the draw () method, regardless of what the shape happens to be. Theoretically, this reduces the amount of time fumbling through manuals and cuts down on syntax errors.

Second, one of the most important Object-Oriented concepts is that every class must be responsible for its own actions. Thus, even though the class must provide a method called draw (), the class must provide its own implementation of the code. For example, a class called Circle and a class called Rectangle both have a draw () method; however, the Circle class obviously has code to draw a circle and, as expected, the Rectangle class has code to draw a rectangle. When you ultimately create classes called Circle and Rectangle, which are subclasses of Shape, these classes must implement their own version of Draw (see Figure 1).

Figure 1: An Abstract Class Hierarchy.

In this way, you have a Shape framework that is truly polymorphic. The Draw method can be invoked for every shape in the system, and invoking this method for each shape produces a different result. Invoking the Draw method on a Circle object draws a circle and invoking the Draw method on a Rectangle object draws a rectangle. In essence, sending a message to an object evokes a different response, depending on the object. This is the essence of polymorphism.

circle.draw();       // draws a circle
rectangle.draw();    // draws a rectangle

Look at some code to illustrate how Rectangle and Circle conform to the Shape contract. Listing 1 contains the code for the Shape class:

Listing 1

public abstract class Shape {
   public abstract void draw();

Note that the class does not provide any implementation for draw (); basically, there is no code and this is what makes the method abstract (providing code would make the method concrete). There are two reasons why there is no implementation. First, Shape does not know what to draw, so you could not implement the draw () method even if you wanted to. Second, we want the subclasses to provide the implementation.

Look at the Circle and Rectangle classes:

Listing 2

public class Circle extends Shape {
   public void draw() {System.out.println ("Draw a Circle");}
public class Rectangle extends Shape {
   public void draw() { System.out.println ("Draw a Rectangle");}

Note that both Circle and Rectangle extend (inherit from) Shape. Also notice that they provide the actual implementation (in this case, the implementation is pretty trivial). Here is where the contract comes in. If Circle inherits from Shape and fails to provide a draw () method, Circle won't even compile. Thus, Circle would fail in its attempt to satisfy the contract with Shape. A project manager can require that programmers creating shapes for the application must inherit from Shape. By doing this, all shapes in the application will have to provide a draw () method that complies with the Shape contract.

Note: If Circle does indeed fail to implement a draw () method, Circle will be considered abstract. Thus, yet another subclass must inherit from Circle and implement a draw () method. This subclass would then become the concrete implementation of both Shape and Circle.

Although the concept of abstract classes revolves around abstract methods, there is nothing stopping Shape from actually providing some implementation (the definition for an abstract class is that it contains one or more abstract methods. This implies that an abstract class can provide concrete methods as well). For example, although Circle and Rectangle implement the draw () method differently, they share the same mechanism for setting the color of a shape. So, the Shape class can have a color attribute and a method to set the color. This setColor () method is an actual concrete implementation and can be inherited by both Circle and Rectangle. The only methods that a subclass must implement are the ones that the superclass declares as abstract. These abstract methods are the contract.

Some languages, such as C++, use only abstract classes to implement contracts. Java and C#, however, have another mechanism, which implements a contract: an interface.

Page 1 of 3

This article was originally published on November 8, 2006

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date