Adapter - Programming in the Large with Design Patterns (2012)

Programming in the Large with Design Patterns (2012)

Chapter 4. Adapter

Introduction

Before MP3s, before compact disks, before cassette tapes, the standard format for portable audio was the 8-track tape.

Figure 31 Eight-track tape cartridge

Eight-track tapes were popular in the United States from the mid 60’s to the late 1970’s. During the late 1970’s, smaller more versatile cassette tapes gradually replaced 8-track tapes as the format of choice for portable audio.

During the transition from 8-track tapes to cassette tapes, music consumers faced some tough choices. When should they start buying cassette tapes rather than 8-track tapes? When should they purchase a cassette player? It cost more to be an early adopter, but waiting longer means having more music in the soon-to-be-obsolete format.

Manufactures offered at least a partial solution in the form of an 8-track tape player adapter. The adapter made it possible to play a cassette tape in an 8-track tape player. One end of the adapter plugged into the 8-track tape player and in the middle was a slot for inserting a cassette tape.

Figure 32 Eight-track to cassette adapter

The adapter converted an 8-track interface into a cassette interface. It converted the interface available into the interface needed:

Figure 33 An adapter converts an interface that is available into an interface that is needed

Eight-track tape player adapters smoothed the transition from 8-track tapes to cassettes. Users didn’t have to upgrade their stereo equipment and media at the same time. Adapters allowed them to prolong the life of their 8-track tape players and start purchasing cassettes sooner than they otherwise might have.

The adapter design pattern can do the same for your software investments. A software system consists of numerous modules connected via interfaces. Sometimes a needed service is available but offered through an interface that is incompatible with existing code. When there is a mismatch between the interface available and the interface needed, the adapter design pattern can be used to make the interface available look and act like the interface needed.

Here is an example to illustrate the point.

Imagine you want to write a Java program that displays the contents of your computer’s file system. Because file system data is hierarchical, the most suitable Swing user interface control for this task is the JTree control.

Figure 34 JTree view of a file system

A JTree object is only responsible for the view. The data displayed resides in a separate object, a data model that conforms to the TreeModel interface.

Figure 35 The data model for the JTree view is of type TreeModel

The file system data to be displayed by the proposed Java program resides in a tree of File objects.

Figure 36 File system data resides in objects of type File

An instance of File represents a file or directory in the file system tree. From any one file or directory it is possible to navigate to any other file or directory in the file system. The methods defined for file are similar to those in the interface TreeModel, but unfortunately File doesn’t implement the interface TreeModel. If it did, we could use it directly with JTree. All the data needed to display a tree view of the file system is available from an instance of File, it’s just not available through the needed interface.

This is the perfect opportunity to use the Adapter design pattern. Applying the Adapter pattern here results in an adapter class that converts File’s interface into the TreeModel interface.

The adapter class implements the interface TreeModel and wraps an instance of File. Since it implements TreeModel it can serve as the data model for an instance of JTree. Requests made of the adapter class are delegated to the wrapped instance of File.

Figure 37 Conceptual class diagram for Adapter design pattern

Intent

The Adapter design pattern is useful in situations where an existing class provides a needed service but there is a mismatch between the interface offered and the interface clients expect. The Adapter pattern shows how to convert the interface of the existing class into the interface clients expect.

Solution

There are two different ways of implementing the Adapter design pattern, one using composition and the other using inheritance.

With composition, the adapter class implements the interface clients expect. At runtime the adapter object is initialized with a reference to the service available. Clients call operations on the adapter object and the adapter object forwards these calls to the service available.

Visually, adaptation via composition looks something like:

Figure 38 Adapter pattern using object composition

Figure 39 shows the class diagram for an 8-track to cassette adapter implemented using object composition. Notice that one operation—reverse()—is handled in the adapter class rather than the adapted class. Eight-track tape players don’t have a reverse function. Even though the spirit of the Adapter pattern is to simply pass operations on to the adaptee, the adapter may take responsibility for some or all of an operation.

Figure 39 Eight-track to cassette adapter implemented using object composition

With inheritance, the adapter class implements the interface the client expects (or inherits the interface from an existing class if the programming language being used allows multiple inheritance) and inherits from the class representing the service available. Clients call operations on the adapter object and the adapter object forwards these calls as necessary to the inherited operations of the available service.

Visually, adaptation via inheritance looks something like:

Figure 40 Adapter pattern using class inheritance

Figure 41 shows the class diagram for an 8-track to cassette adapter implemented using class inheritance. Notice that delegate code isn’t needed in the adapter class for operations on the client interface that are identical to operations in the inherited service.

Figure 41 Eight-track to cassette adapter implemented using class inheritance

Implementing the adapter pattern with inheritance rather than composition requires less code when methods in the expected interface are already implemented in the adaptee. However, because inheritance exposes the protected interface of a parent class to subclasses, inheritance also creates the potential for tighter coupling between the adapter and the adaptee. For example, suppose 8-TrackPlayer had a protected field for the position of the playback head:

The adapter class is free to make use of this protected information when implementing its own methods:

public void fastForward() {

timePosition = timePosition + 5;

}

The result is tighter coupling between the adapter and the adaptee. Because higher coupling is typically more harmful than a few extra lines of simple delegate code, composition is usually a better option than inheritance when implementing the adapter design pattern.

In general, when the only purpose of using inheritance is reuse, composition is usually a more appropriate implementation strategy. Situations like this are why some experts encourage designers to: favor object composition over class inheritance (Gamma, et al. 1995), or as I like to say,always consider object composition before resorting to class inheritance.

Sample Code

The sample code in this section implements the file system display example mentioned in the introduction.

Recall that the standard Java user interface component for displaying hierarchical data is JTree. Also recall that JTree is just the view. The data displayed is stored in a separate object that implements the TreeModel interface. The Adapter pattern is needed because File, the class with the file system data, doesn’t implement the TreeModel interface.

In the code below, FileAdapter implements the TreeModel interface and wraps an instance of File. Requests for data from JTree are forwarded to the wrapped instance of File. Note, not all of the methods in the interface TreeModel are implemented by the adapter. Specifically, methods needed to handle dynamic changes to the underlying data model are not implemented. Also note the extra code needed in the delegate methods to make the existing interface function like the expected interface. Delegation isn’t always as simple as calling an identical method of a different name.

import java.awt.*;

import javax.swing.event.*;

import java.io.*;

import javax.swing.*;

import javax.swing.tree.*;

public class FileAdapterExample extends JFrame {

public static void main(String[] args) {

new FileAdapterExample();

}

public FileAdapterExample() {

JTree tree;

FileAdapter treeNode;

Container content = getContentPane();

// start at the root of the file system

File f = new File("/");

treeNode = new FileAdapter (f);

tree = new JTree(treeNode);

content.add(new JScrollPane(tree),BorderLayout.CENTER);

setSize(600, 375);

setVisible(true);

}

}

class FileAdapter implements TreeModel {

// root of displayed tree

private File root;

public FileAdapter (File file) {

root = file;

}

public Object getRoot() {

return root;

}

public Object getChild(Object parent, int index) {

File files[] = ((File)parent).listFiles();

return files[index];

}

public int getChildCount(Object parent) {

File files[] = ((File)parent).listFiles();

if (files == null)

return 0;

else

return files.length;

}

public boolean isLeaf(Object node) {

return !((File)node).isDirectory();

}

public void valueForPathChanged(TreePath path, Object newValue) { }

public int getIndexOfChild(Object parent, Object child) {

return 0;

}

public void addTreeModelListener(TreeModelListener l) { }

public void removeTreeModelListener(TreeModelListener l) { }

}

Discussion

The Adapter design pattern provides a convenient solution to the problem of how to upgrade interdependent system components at different rates. For example, suppose you have a program with two classes A and B as shown in the following image. Class A is dependent on class B because a method in class A calls a method in class B. Furthermore, suppose you plan to rename the method g() in B to h(). This will necessitate changes to both A and B:

Any change to software introduces some risk, and the more components or classes that change at one time, the greater the risk. One way to reduce this risk is to integrate and test the changes one component at a time.

Let’s say you decide to integrate and test changes to B before making changes to A. This can be accomplished by making the changes to B and then adding an adapter to make the new interface of B look like the old one.

The changes to B can be integrated and fully tested before integrating the updates to A. When you are ready to integrate changes to A, simply remove the adapter and allow the new version of A to interact directly with the updated version of B.

Related Patterns

The Adapter, Bridge, Proxy and Facade patterns have the same basic structure and collaboration. Requests to a subject object are forwarded on to a delegate object.

Figure 42 Several design patterns are based on object composition

While the structure and interaction diagrams for these four patterns may look similar, the patterns are distinguished by their intent or purpose.

The intent of the Adapter pattern is to resolve incompatibilities between two existing interfaces. With the Adapter pattern Client and Delegate are two existing classes that needed to work together but can’t because of incompatible interfaces. The Adapter pattern converts the interface of Delegate into the existing SubjectInterface so that Delegate objects can be used (or reused) with clients expecting objects that implement SubjectInterface.

The intent of the Bridge pattern is to separate an interface from its implementation so the two can vary independently. With the Bridge pattern, in contrast with the Adapter pattern, the interfaces SubjectInterface and DelegateInterface don’t exist at the time the pattern is applied. The design of these two interfaces is the whole point of the Bridge pattern. The result of using the Bridge pattern is the ability for the abstraction defined by SubjectInterface and implementation defined in Delegate to evolve independently.

The intent of the Proxy pattern is to provide a surrogate or placeholder for another object in order to control access to it. With the Proxy pattern, SubjectInterface and DelegateInterface are one and the same. Classes Subject and Delegate implement the same interface. Subject controls access to Delegate and is therefore considered a proxy for Delegate.

The intent of the Facade pattern is to provide a simplified interface to one or more classes. With the Facade pattern, SubjectInterface is the new simplified interface that is created in order to simplify access to Delegate classes. Façade doesn’t hide or encapsulate Delegate. Delegate is still accessible through the more complex DelegateInterface.

Another pattern that shares some similarities with the Adapter pattern is Decorator. Both Decorator and Adapter wrap another object, but they do so for different reasons. Adapter wraps an object to change its interface. Decorator wraps an object to add behavior.