Abstract classes and interfaces practicum

Move from theory to practice on when to employ abstract classes vs. interfaces

Readers have returned quite a bit of feedback concerning, "Abstract Classes Vs. Interfaces" from April 2001.

A recurring request asks to see a complete example of using interfaces and abstract classes. Based on the feedback, the original answer proved a bit too theoretical, so in this follow-up Java Q&A, I will bring the discussion down to earth by presenting a framework that employs both interfaces and abstract classes.

When dealing with network communication, you will find that communication is often accomplished through the exchange of key/value pairs. Both HTTP POST and HTTP GET employ key/value pair communication. Key/value pair communication is also the mechanism of choice for communicating with EAI servers such as WebMethods. You even see key/value pairs when you use Java properties. Key/value pairs pop up everywhere.

Key/value pair communication's popularity results from its simple mechanism to assign meaning to a data value. While it is simple, each protocol has its own way of putting the data pairs over the wire. If you want to communicate with a Web server, you use a URLConnection and talk HTTP with the server. Other types of communication will require you to use some other component. For this Q&A , I demonstrate how you can use interfaces and abstract classes in order to define a framework that can work with any server that expects key/value pair messages.

You can make two abstractions about key/value communication. First, the key/value pairs you send to a listener together make up a Message. Second, messages are sent to the server over some sort of protocol. Abstractly, you can call a protocol a MessageBus. So, for example, when communicating with a Web server, you send messages over an HTTP MessageBus.

Both the message you send and the mechanism that sends the message may change often, or at least from program to program. When you know that something will change often, it is a good candidate for an interface.

Let's consider each interface our messaging framework needs.

MessageBus

In the code below you see that a MessageBus knows how to send a 2-dimensional array of key/value pairs to some listener. Notice that the interface doesn't say how the values will be sent or to whom. Instead, those details are left to the class that implements the interface:

public interface MessageBus {
    public String sendMessage( String [][] values ) throws BusException;
}

The use of an interface here is very powerful. You can have an implementation that uses URLConnections to talk HTTP. Another implementation might use a proprietary socket protocol. Yet another may just log the values to a flat file or database.

In any case, the interface allows you to create many different implementations. More so, if you program to the interface instead of some specific implementation, polymorphism allows you to freely swap in any implementation into your program.

So, for example, you can swap out an HTTP MessageBus implementation for a logging implementation if you wish to debug your application. Such an approach allows you to change the way your program works without requiring all kinds of changes to the original application. Whenever your program needs to support a new type of communication, you can simply create a new MessageBus implementation and you're done.

Message

In the next example, a Message simply knows how to send itself across a MessageBus. The specific implementation will provide whatever necessary to retrieve the Message's values.

Note: you won't find a getter for the Message's values. Instead, we allow the Message to fully encapsulate its data and trust it to place itself onto the MessageBus appropriately:

public interface Message {
    public String send( MessageBus mb ) throws BusException;
}

Just as MessageBus allows you to add new communication mechanisms to a program expecting MessageBus, Message allows you to add new Message types to your programs at any time.

HttpMessageBus

Let's look at a MessageBus implementation that POSTs its messages using HTTP:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
 * HttpMessageBus is an implementation of MessageBus that employs HTTP POST
 * to send messages.
 * @author  Tony Sintes JavaWorld Q&A
 */
public class HttpMessageBus implements MessageBus {
    private String _url;
    
    private final static String _KEY_VALUE_DELIM = "=";
    private final static String _NEW_KEY_VALUE   = "&";
    private final static String _ENCODING = "application/x-www-form-urlencoded";
    private final static String _TYPE     = "Content-Type";
    private final static int _KEY   = 0;
    private final static int _VALUE = 1;
    
    public HttpMessageBus( String url ) 
    {
        _url = url;
    }
    public String sendMessage( String[][] values ) throws BusException 
    {
        String post = _formulatePOST( values ); 
        try 
        {
            return _sendPOST( post );
        } 
        catch( MalformedURLException exception ) 
        {
            throw new HttpBusException( exception.getMessage() );
        } 
        catch( IOException exception ) 
        {
            throw new HttpBusException( exception.getMessage() );
        }
    }
    
    private String _formulatePOST( String [][] values ) {
        if( ( values == null ) || ( values.length == 0 ) ) 
        {
            return "";
        }
        
        StringBuffer sb = new StringBuffer();
        String new_pair = "";
        
        for( int i = 0; i < values.length; i ++ ) 
        {
            sb.append( new_pair );
            sb.append( URLEncoder.encode( values[i][_KEY] ) ); 
            sb.append( _KEY_VALUE_DELIM ); 
            sb.append( URLEncoder.encode( values[i][_VALUE] ) );
            new_pair = _NEW_KEY_VALUE;
        }
     
        String post = sb.toString();
        return post;
    }
    
    private String _sendPOST( String post ) throws MalformedURLException, IOException 
    {    
        URLConnection conn = _setUpConnection();
        _write( post, conn );
        return _read( conn );
    }
    
    private URLConnection _setUpConnection() throws MalformedURLException, IOException 
    { 
        URL url = new URL( _url );
        URLConnection conn = url.openConnection();
        
        conn.setDoInput ( true );
        conn.setDoOutput ( true );
        conn.setUseCaches ( false );
        conn.setRequestProperty( _TYPE, _ENCODING );
       
        return conn;
    }
    
    private void _write( String post, URLConnection conn ) throws IOException
    {
        DataOutputStream output = new DataOutputStream ( conn.getOutputStream() );
        output.writeBytes( post );
        output.flush ();
        output.close (); 
    }
    
    private String _read( URLConnection conn ) throws IOException
    {   
        BufferedReader input = 
            new BufferedReader( new InputStreamReader( conn.getInputStream() ) );
        String temp_string;
        StringBuffer sb = new StringBuffer();
        while ( null != ( ( temp_string = input.readLine() ) ) )
        {
            sb.append( temp_string );
        }
        input.close ();
        
        return sb.toString();
    }
    
}

I'll leave it as an exercise for the reader to study this code. For help, you'll find plenty of JavaWorld Java Tips (see Resources) that cover POSTing. I will, however, point out how much is hidden behind the simple MessageBus interface. The HttpMessageBus simply takes a URL at construction. All values sent to its sendMessage() method get sent to that URL. Everything else is hidden behind the interface.

AbstractMessage

After writing a few Message implementations, you will find the send() method breaks down into two high-level steps:

  1. Turn the Message's internal data into a 2-dimensional array
  2. Send that array across the MessageBus by calling sendMessage() on the bus and passing it the 2-dimensional array

Each time you write a new Message implementation, you will write similar send() methods.

Through the careful use of abstract classes, you can make it easy to write new Message implementations and remove redundant code. Consider the following AbstractMessage definition:

public abstract class AbstractMessage implements Message 
{
    public String send(MessageBus mb) throws BusException 
    {
        String [][] values = values();
        String response = mb.sendMessage( values );
        return response;
    }
    
    protected abstract String [][] values();
    
}

Above, you see that AbstractMessage provides a default implementation of the send() method. The default implementation provides three benefits:

  • You don't have to write the same method over and over again. Instead, you now have a default implementation that accomplishes the two high-level tasks defined earlier.
  • You can now write a new Message simply by providing an implementation for values(). That way, your subclasses only need to know how to represent themselves as an array of key/value pairs. Subclasses do not need to concern themselves with how to send themselves over the bus. The send action is the same for all Messages.

    An important use of inheritance is for programming by difference. Programming by difference defines how a subclass differs from its parent. When dealing with Messages, a Message differs from its parent only by the data that it holds. It does not differ in how it is sent over the bus. Using an abstract class allows us to cleanly use inheritance.

  • The AbstractMessage defines a protocol for defining new Messages through subclasses. It is easy for a programmer to see which methods he needs to re-implement when he creates a subclass. While this example is simple, such programming queues can make subclassing a complicated class much simpler.

As a class becomes larger, the previous three points will become much more pronounced.

Example Messages

Consider a Website that offers stock quotes. When you look up a ticker symbol, you make a POST to some URL. Along with that URL, you'll probably append the name of a ticker symbol.

The key/value pair may look something like:

ticker=bvsn

Here ticker is the key and the value is bvsn. The key tells the receiver that the value, bvsn, is a ticker symbol.

In the case of Yahoo's ticker look up service, you send the URL:

http://finance.yahoo.com/q

two key/value pairs:

  • s - the ticker symbol
  • d - the level of lookup (basic, detailed, etc.)

So, to look up bvsn you would need to make the following POST:

http://finance.yahoo.com/q?s=bvsn&d=v1

In contrast, with Quicken's ticker lookup service you send the URL:

http://www.quicken.com/investments/quotes/

one key/value pair:

symbol - the ticker symbol

Therefore, in Quicken, to look up bvsn you would make the following POST:

http://www.quicken.com/investments/quotes/?symbol=bvsn

Here are the implementations of the two messages:

public class YahooStockQuote extends AbstractMessage {
    private String _ticker;
    
    private final static String _TICKER_KEY = "s";
    private final static String _D_VALUE    = "v1";
    private final static String _D_KEY      = "d";
    
    public YahooStockQuote( String ticker ) 
    {
        _ticker = ticker;
    }
    public void setTicker( String ticker ) 
    {
        _ticker = ticker;
    }
    
    protected String [][] values() 
    {
        String [][] values = {
            { _TICKER_KEY, _ticker },
            { _D_KEY, _D_VALUE }
        };
        return values;
    }
}

And:

public class QuickenStockQuote extends AbstractMessage 
{
    private String _ticker;
    
    private final static String _TICKER_KEY = "symbol";
    
    public QuickenStockQuote( String ticker ) 
    {
        _ticker = ticker;
    }
    public void setTicker( String ticker ) 
    {
        _ticker = ticker;
    }
    
    protected String [][] values() 
    {
        String [][] values = {
            { _TICKER_KEY, _ticker }
        };
        return values;
    }
}

These Messages simply know how to hold onto their data and construct 2-dimensional key/value pair arrays.

An example quote program

Consider the following example quote program:

public class QuoteGetter
{
  public final static void main( String [] args )
  {
    if( args.length != 2 )
    {
      System.out.println( "Incorrect Useage:" );
      System.out.println( "java QuoteGetter 
" );
      return;
    }
    String service = args[0];
    String ticker  = args[1];
    Message message = null;
    MessageBus bus  = null;
    if( service.equalsIgnoreCase( "quicken" ) )
    {
      bus = new httpMessageBus( "http://www.quicken.com/investments/quotes/" );
      message = new QuickenStockQuote( ticker );
    }
    else // default to yahoo
    {
      bus = new HttpMessageBus( "http://finance.yahoo.com/q" );
      message = new YahooStockQuote( ticker );
    }
    try
    {
      String response = sendMessage( message, bus );
      System.out.println( response );
    }
    catch( Exception ignore )
    {
      System.out.println( "Lookup Failed for: " + ticker );
      ignore.printStackTrace();
    }
  }
  private static String sendMessage( Message message, MessageBus bus )
    throws BusException
  {
    return message.send( bus );
  }
}

This main creates the appropriate Message and MessageBus. It then sends the Message by calling sendMessage(), which serves as an example of programming to an interface instead of an implementation. You would say that sendMessage() is programmed to an implementation if it specifically expects a YahooStockQuote or an HttpMessageBus.

Instead, the method is programmed to the interface. As a result you can pass any implementation of the interface to the method, making it much more generic.

Wrap it up

I've used this messaging framework successfully in my day-to-day work. Through interfaces I have been able to seamlessly change the communication mechanism from HTTP POST to a proprietary protocol. I've also been able to use the Message interface to add new messages to my application with ease. Furthermore, by first writing an abstract Message base class, I can more easily write those new methods and reuse them within other applications.

Hopefully this detailed example helps ground the earlier, more theoretical discussion about interfaces and abstract classes. Be sure to download the source code included with this Q&A. The source includes the entire messaging framework as well as some additional classes not mentioned in this text.

Tony Sintes is a senior principal consultant at BroadVision. Tony, a Sun-certified Java 1.1 programmer and Java 2 developer, has worked with Java since 1997.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies