Chapter 9. The Adapter Pattern

The Adapter pattern converts the programming interface of one class into that of another. You use adapters whenever you want unrelated classes to work together in a single program. The concept of an adapter is thus pretty simple: You write a class that has the desired interface and then make it communicate with the class that has a different interface. There are two ways to do this: by inheritance and by object composition. In the first, you derive a new class from the nonconforming one and add the methods that you need to make the new derived class match the desired interface. In the second, you include the original class inside the new one and create the methods to translate calls within the new class. These two approaches, termed class adapters and object adapters, are both fairly easy to implement in Java.

Moving Data between Lists

Let's consider a simple Java program that allows you to enter names into a list and then select some of those names to be transferred to another list. The initial list in the example consists of a class roster, and the second list consists of class members who will be doing advanced work.

In this simple program, shown in Figure 9.1, you enter names into the top entry field and then click on Insert to move the names into the left-hand list box. Then, to move a name to the right-hand list box, you click on it and then click on Add. To remove a name from the right-hand list box, click on it and then on Remove. This moves the name back to the left-hand list.

A simple program to enter and choose names.

Figure 9.1. A simple program to enter and choose names.

This is a very simple program to write in Java 1.2. It consists of a GUI creation constructor and an actionListener routine for the three buttons. A Command pattern, or even a simple set of if statements, can then call the following actions on the two lists:

//add a name to the left-hand list
  private void addName() {
        if (txt.getText().length()> 0) {
            leftList.add(txt.getText());
            txt.setText("");
        }
  }
//--------------
//move a name from the left to the right list box
  private void moveNameRight() {
        String sel[] = leftList.getSelectedItems();
        if (sel != null) {
            rightList.add(sel[0]);
            leftList.remove(sel[0]);
        }
}
//--------------
//move a name from the right to the left list box
  public void moveNameLeft() {
        String sel[] = rightList.getSelectedItems();
        if (sel != null) {
            leftList.add(sel[0]);
            rightList.remove(sel[0]);
        }
}

This program, cal1led TwoList.java, is included on the CD-ROM that accompanies this book.

Using the JFC JList Class

You will note that the previous example used the AWT List class. This is all quite straightforward, but suppose you would like to rewrite the program using the Java Foundation Classes (JFC or "Swing"). Most of the methods you use for creating and manipulating the user interface remain the same. However, the JFC JList class is markedly different from the AWT List class. In fact, because the JList class was designed to represent far more complex kinds of lists, there are virtually no methods in common between the classes.

AWT List Class JFC JList Class
add(string); na
remove(String) na
String[] getSelectedItems() Object[] getSelectedValues()

Both classes have quite a number of other methods, and almost none of them are closely correlated. However, since we have already written the program once and make use of two different list boxes, writing an adapter to make the JList class look like the List class will provide a rapid solution to our problem.

The JList class is a window container that has an array, Vector, or other ListModel class associated with it. It is this ListModel that actually contains and manipulates the data. Further, the JList class does not contain a scroll bar but instead relies on being inserted in the viewport of the JScrollPane class. Data in the JList class and its associated ListModel are not limited to strings; they may be almost any kind of objects, as long as you provide the cell drawing routine for them. This makes it possible to have list boxes with pictures illustrating each choice in the list.

In this case, we are going to create a class that only emulates the List class, which here needs only the three methods shown in the previous table.

We can define the needed methods as an interface and then make sure that the class that we create implements those methods.

public interface awtList {
     public void      add(String s);
     public void      remove(String s);
     public String[]  getSelectedItems();
}

Interfaces are important in Java because Java does not allow multiple inheritance as C++ does. So you cannot create a class that inherits from JList and List at the same time. However, you can create a class that inherits from one class hierarchy and implements the methods of another. In most cases, this is quite a powerful solution. Thus, by using the implements keyword, we can have the class take on the methods and appearance of being a class of either type.

The Object Adapter

In the object adapter approach, shown in Figure 9.2, we create a class that contains a JList class but which implements the methods of the previous awtList interface. Since the outer container for a JList is not the list element at all but the JScrollPane that encloses it, we are really adding methods to a subclass of JScrollPane that emulate the methods of the List class. These methods are in the interface awtList.

An object adapter approach to the list adapter.

Figure 9.2. An object adapter approach to the list adapter.

So, our basic JawtList class looks as follows:

public class JawtList extends JscrollPane
implements ListSelectionListener, awtList {
    private JList listWindow;
    private JListData listContents;

    public JawtList (int rows) {
        listContents = new JListData();
        listWindow   = new JList(listContents);
        listWindow.setPrototypeCellValue("Abcdefg Hijkmnop");
        getViewport().add(listWindow);
    }
public class JawtList extends JScrollPane
implements ListSelectionListener, awtList {
    private JList listWindow;
    private JListData listContents;

    public JawtList (int rows) {
        listContents = new JlistData();
        listWindow = new JList (listContents);
        listWindow.setPrototypeCellValue("Abcdefg Hijkmnop");
        getViewport().add(listWindow);
    }
//---------------------------------------------------
    public void add(String s) {
        listContents.addElement(s);
    }
//---------------------------------------------------
    public void remove(String s) {
        listContents.removeElement(s);
    }
//---------------------------------------------------
    public String[] getSelectedItems() {
        Object[] obj = listWindow.getSelectedValues();
        String[] s   = new String[obj.length];
        for (int i   = 0; i < obj.length; i++)
            s[i] = obj[i].toString();
        return s;
    }
//---------------------------------------------------
    public void valueChanged(ListSelectionEvent e) {
    }
}

Note, however, that the actual data handling takes place in the JListData class. This class is derived from the AbstractListModel, which defines the methods listed in the following table:

Method Purpose
addListDataListener(l) Adds a listener for changes in the data.
removeListDataListener(1) Removes a listener.
fireContentsChanged(obj,min,max) Tells the JList window to redraw. Calls this after any change occurs between the two indexes min and max.
fireIntervalAdded(obj,min,max) Tells the JList window to redraw. Calls this after any data have been added between min and max.
fireIntervalRemoved(obj,min,max) Tells the JList window to redraw. Calls this after any data have been removed between min and max.

The three fire methods provide the communication path between the data stored in the ListModel and the actual displayed list data. Firing them causes the displayed list to be updated.

In this case, the addElement, and removeElement methods are all that are needed, although you could imagine a number of other useful methods. Each time that we add data to the data vector, we call the fireIntervalAdded method to tell the list display to refresh that area of the displayed list.

public class JListData extends AbstractListModel {
    private Vector data;

    public JListData() {
        data = new Vector();
    }
//--------------
    public int getSize() {
        return data.size();
    }
//--------------
    public Object getElementAt (int index) {
        return data.elementAt(index);
    }
//--------------
    public void addElement(String s) {
        data.addElement(s);
        fireIntervalAdded(this, data.size()-1, data.size());
    }
//--------------
    public void removeElement(String s) {
        data.removeElement(s);
        fireIntervalRemoved(this, 0, data.size());
    }
}

The Class Adapter

In Java, the class adapter approach isn't all that different from the object adapter. If we create a class JawtClassList that is derived from JList, then we have to create a JScrollPane in our main program's constructor.

         leftList        =  new JClassAwtList(15);
         JScrollPane lsp =  new JScrollPane();
         pLeft.add("Center", lsp);
         lsp.getViewport().add(leftList);

This class-based adapter is much the same, except that some of the methods now refer to the enclosing class instead of an encapsulated class.

public class JclassAwtList extends Jlist
    implements ListSelectionListener, awtList {
    private JListData listContents;

    public JclassAwtList (int rows) {
        listContents = new JListData();
        setModel(listContents);
        setPrototypeCellValue("Abcdefg Hijklmnop");
}

This is illustrated in Figure 9.3.

A class adapter approach to the List adapter.

Figure 9.3. A class adapter approach to the List adapter.

Some differences between the List and the adapted JList classes are not so easy to adapt, however. The List class constructor allows you to specify the length of the list in lines. There is no way to specify this directly in the JList class. You can compute the preferred size of the enclosing JScrollPane class based on the font size of the JList, but depending on the layout manager, this might not be honored exactly.

The example class JawtClassList, called by JTwoClassList, is on the accompanying CD-ROM.

There are also some differences between the class and the object adapter approaches, although they are less significant than in C++.

  • The class adapter

    • Won't work when you want to adapt a class and all of its subclasses, since you define the class that it derives from when you create it.

    • Lets the adapter change some of the adapted class's methods but still allows the others to be used unchanged.

  • The object adapter

    • Could allow subclasses to be adapted by simply passing them in as part of a constructor.

    • Requires that you specifically bring to the surface any of the adapted object's methods that you wish to make available.

Two-Way Adapters

The two-way adapter is a clever concept that allows an object to be viewed by different classes as being either of type awtList or type JList. This is most easily carried out by a class adapter, since all of the methods of the base class are automatically available to the derived class. However, this can work only if you do not override any of the base class's methods with methods that behave differently. As it happens, the JawtClassList class is an ideal two-way adapter because the List and JList classes have no methods in common.

Note in the UML diagram in Figure 9.3 that JawtClassList inherits from JList and implements awtList. Thus you can refer to the awtList methods or to the JList methods equally conveniently and treat the object as an instance either of JList or of awtList.

Pluggable Adapters

A pluggable adapter is an adapter that adapts dynamically to one of several classes. Of course, the adapter can adapt only to classes that it can recognize, and usually the adapter decides which class it is adapting to based on differing constructors or setParameter methods.

Java has yet another way for adapters to recognize which of several classes it must adapt to: reflection. You can use reflection to discover the names of public methods and their parameters for any class. For example, for any arbitrary object you can use the getClass method to obtain its class and the getMethods method to obtain an array of the method names.

JList list = new JList();
      Method[] methods = list.getClass().getMethods();
      //print out methods
      for (int i = 0; i < methods.length; i++) {
           System.out.println(methods[i].getName());
           //print to parameter types
           Class cl[] = methods[i].getParameterTypes();
           for(int j = 0; j < cl.length; j++)
               System.out.println(cl[j].toString());
}

A "method dump" like the one produced by this code can generate a very large list of methods. It is easier to use if you know the name of the method that you are looking for and simply want to find out the arguments that it requires. From that method signature, you can then deduce the adaptations that you need to carry out.

However, because Java is a strongly typed language, you more likely would simply invoke the adapter using one of several constructors, with each constructor tailored for a specific class that needs adapting.

Adapters in Java

In a broad sense, a number of adapters are already built into the Java language. In this case, the Java adapters serve to simplify an unnecessarily complicated event interface. One of the most commonly used of these Java adapters is the WindowAdapter class.

One inconvenience of Java is that windows do not close automatically when you click on the Close button or window Exit menu item. The general solution to this problem is to have your main Frame window implement the WindowListener interface and leave all of the Window events empty except for windowClosing.

public void mainFrame extends Frame
        implements WindowListener {

    //frame listens for window events
    public void mainFrame() {
        addWindowListener(this);
    }

    public void windowClosing(WindowEvent wEvt) { 
        System.exit(0);   //exit on System exit box click
    }
    public void windowClosed(WindowEvent wEvt) {}
    public void windowOpened(WindowEvent wEvt) {}
    public void windowIconified(WindowEvent wEvt) {}
    public void windowDeiconified(WindowEvent wEvt) {}
    public void windowActivated(WindowEvent wEvt) {}
    public void windowDeactivated(WindowEvent wEvt) {}
}

As you can see, this code is awkward and hard to read. The Window Adapter class is provided to simplify this procedure. This class contains empty implementations of all seven of the previous WindowEvents. You then need only to override the windowClosing event and insert the appropriate exit code.

One such simple program is shown next.

//illustrates using the WindowAdapter class
//make an extended window adapter which
//This class closes the frame
//when the closing event is received
class WindAp extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
}
//==============
public class Closer extends Frame {
    public Closer() {
        WindAp windap = new WindAp();
        addWindowListener(windap);
        setSize(new Dimension(100,100));
        setVisible(true);
    }
    static public void main(String argv[]) {
        new Closer();
    }
}

You can, however, make a much more compact but less readable version of the same code by using an anonymous inner class.

//create window listener for window close click
    addWindowListener(new WindowAdapter()
    {
        public void windowClosing(WindowEvent e)
            {System.exit(0);} 
    });

Adapters like these are common in Java when a simple class can be used to encapsulate a number of events. They include ComponentAdapter, ContainerAdapter, FocusAdapter, KeyAdapter, MouseAdapter, and MouseMotionAdapter.

Thought Question

  1. How would you write a class adapter to make the JFC JTable look like a two-column list box?

Programs on the CD-ROM

Program Description

AdapterTwoListsTwolist.java

An AWT version of two lists.

AdapterJTwoListsJTwoList.java

An object adapter version.

AdapterJtwoClassListJTwoClassList.java

A class adapter version.

AdaptershowMethods.java

Illustrates reflection methods.

In addition, the Window adapter is illustrated in both the JTwoList and JTwoClassList programs on the accompanying CD-ROM.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset