Des (deskitty) wrote,
Des
deskitty

  • Mood:

An example of why multiple inheritance can be a good thing

In one of my (Java) projects at work, we have an interface which represents the contents of a "savable window", which can be modified by the user, and where changes should be saved back to the underlying object when the user is done (usually indicated via an "OK" button).

The (vastly simplified) implementation looks like this:

/** Contains all the widgets, fields, etc. for a window.  It can be "saved" 
 * (i.e. asked to save user changes back to the model, and save the model to
 * disk) by calling its save() method. */
public interface Savable /* extends JPanel */ {
    public void save();
    ...
}

/** A toplevel window which contains a Savable.  It may also provide "OK"
 * and "Cancel" buttons, or perform boilerplate functionality such as prompt
 * the user to save if he/she tries to close the window when it has unsaved
 * data.  The boilerplate stuff is irrelevant for now, except to emphasize that
 * CMWindow is not a small class. */
public class CMWindow extends JFrame {
    private Savable _contents;

    public CMWindow(Savable s) {
        _contents = s;
        getContentPane().add(s);
        // Add "OK" and "Cancel" buttons, and the like
    }

    public void save() {
        _contents.save();
    }
    ...
}


The CMWindow class has a lot of administrative code for things like, "You have unsaved changes. Do you want to save them before closing?". Objects implementing the Savable interface extend a base GUI class, generated by an automated tool, which contains and lays out all the GUI components for the window.

Suppose, for example, you have a Preferences class which implements Savable. Preferences is a GUI widget that contains various fields that display/edit application preferences. You would construct and show a window with those preferences by doing:

CMWindow win = new CMWindow(new Preferences());
win.setVisible(true);


The problem is this: I'd like to allow other parts of the program to receive an event (using Java's listener pattern) whenever a Savable is save()d.

In order to do this, I would have to do one of three things:
  1. Add the notification facility to CMWindow itself.

  2. Require everything that implements Savable to implement its own notification functionality.

  3. Make CMWindow.Savable be a class, and implement notification functionality there.

Option #1 isn't viable, without some major refactoring. I would have to find every place where a Savable calls save() on itself, and change it to call save() on its parent CMWindow. Even despite the refactoring, this is rather ugly. At the very least, I'm replacing save() with something like ((CMWindow)getTopLevelAncestor()).save().

Option #2 would be a pain, because it requires a lot of boilerplate code in each Savable. I would have to write my own EventListenerList-derived class that knows how to fire SaveEvents, then go through each Savable and add the appropriate methods (add/removeListener), add another field for the listener list, and manually fire the event.

Option #3 would work ... except one of the useful properties of Savable as an interface is the ability to embed implementations of it (which derive indirectly from JComponent) in other GUI widgets. I could preserve this ability by making Savable itself derive from JComponent, but then I would end up writing more boilerplate code to connect the GUI generated by the GUI tool with the Savable. This is better than #2, but any boilerplate code makes my hair curl.

I think you'll agree none of these solutions are optimal.

Were I doing this in C++ (or any language with multiple inheritance), I could just do the following:
  • Add the appropriate listener machinery to the Savable base class
  • Create a new pure virtual (or abstract) method in Savable called doSave(), realSave(), or something similar.
  • Rename the save() methods in all the Savable-derived classes to doSave().
  • Change Savable.save() to be a non-virtual (or final) method that calls doSave(), then fires the SaveEvent.

Were this option available, it would be by far the least invasive. The most I would have to do to my Savable-derived classes is rename a method, which is much safer than adjusting its implementation. If you screw up a rename, the compiler will usually catch it ("does not implement abstract method foo", or whatever), but if you accidentally miss one of the classes in an implementation change, or your change introduces a bug in one of them ... well, maybe the compiler will catch it, and maybe it won't.

Rather than risking one of the three feasible options this late in the game, I'm just special-casing it for the specific situations I need. It's sub-optimal (because I have to do it in a couple different places), but hey, it works. For now. :p

Edit: Cleaned up the sample code and changed a bit of wording to (hopefully) clarify the design.

-- Des
Tags: geeky, java, programming languages
Subscribe

  • (no subject)

    Well, I'm off to Dreamwidth. I hope to see you all there! Nice knowing you, LJ. It's been grand. — Des

  • A fresh start?

    So I'm thinking of moving away from LJ. Every time I glance at my ad blocker, there are an uncomfortably-large number of advertising and tracking…

  • 2012: Ramp It Up

    It’s that time of the year again -- another year has passed, and as usual, I don’t finish reflecting on it until the first 3 months of the following…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 4 comments