?

Log in

No account? Create an account
An example of why multiple inheritance can be a good thing - The Desian Universe
Links Home / GitHub January 2017
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 
 
 
 
deskitty
deskitty
Des
Wed, Jan. 31st, 2007 03:36 pm
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: , ,
Current Mood: annoyed annoyed

4CommentReplyShare

kion
kion
Kevin Kress
Thu, Feb. 1st, 2007 02:05 am (UTC)

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.


ReplyThread
kion
kion
Kevin Kress
Thu, Feb. 1st, 2007 02:08 am (UTC)

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


Just add this code to your main window Save() call

* Create a new pure virtual (or abstract) method in Savable called doSave(), realSave(), or something similar.

Rename your Saveable interface "save" method to doSave()

* Rename the save() methods in all the Savable-derived classes to doSave().

Same step works in java.

* Change Savable.save() to be a non-virtual (or final) method that calls doSave(), then fires the SaveEvent.

Final is a keyword in java your new Save method would look like this:

public final void Save()
{
//fire event code
doSave(); //calls proper virtual function
}


ReplyThread
deskitty
deskitty
Des
Thu, Feb. 1st, 2007 02:51 am (UTC)

Just add this code to your main window Save() call

Sometimes the Savable objects call their own save(), so the CMWindow isn't always aware of when a save happens.(See Option #1.)

Final is a keyword in java your new Save method would look like this:

Savable is an interface. AFAIK you can't add method implementations to an interface.

Pursuant to our discussion on IRC: I don't think data models are quite what I want, either.

The class hierarchy looks like this:

JFrame -> CMWindow
JPanel -> [class generated by UI tool] -> [class that implements Savable]

CMWindow has a Savable object, which goes into its contentPane (along with other boilerplate things like "OK" and "Cancel" buttons).

A bit more context/example: I have a Client object representing a therapy client. The Client has a first name, last name, birthdate, etc. A Savable for this client object would be a form that allows the user to fill in all these fields. When the user presses the "OK" button, the Savable's save() method gets called. The save() method pushes the user's changes back to the Client object and saves the Client object to disk.

I want other, indeterminate parts of the program to be notified when the save() happens. (Said other parts--for example, parent windows--would be responsible for installing their own listeners.)


ReplyThread Parent
stargazr417
stargazr417
Jesse
Fri, Feb. 2nd, 2007 01:01 am (UTC)

Woah...that went so far over my head I think it'll take a couple of weeks before I feel the slightest bit intelligent again, heh...

But suffice it to say that I'm duly impressed! ^^


ReplyThread