Java Programming Tip: Configure Swing Classes Instead of Extending Them
I've worked on a number of Swing-based applications over the years, both in a professional capacity and on my own, and I've seen a variety of approaches taken to building up what can only be called GUI frameworks dedicated to the construction of each particular application. There are two general types of approach that I have seen, one of which is much better at expanding and growing over time than the other. Sadly, the one that is most commonly used seems to be the one that is not so great at growing, but to the casual observer might seem the more obvious choice.
The two approaches I'm referring to are Composition and Inheritance.
These two design philosophies are by no means specific to GUI frameworks. In fact, there are reams of text written on just comparing the virtues of the two approaches. However, in the context of Swing, the question can be seen in a new light. Composition is a much stronger approach for building Swing frameworks with longevity, hands down.
What on earth am I talking about, you ask? Well, in general the idea of Inheritance is that you extend a class and add functionality to it. In terms of Swing classes, this usually translates into extending a class to specialize it into the exact component you want. Composition, in contrast, is all about keeping references to objects from your own objects, which in Swing translates into building up a component hierarchy by calling the various "set" methods to configure the constituent components rather than extending them to configure those same settings.
Let's speak of this in more concrete terms. We all know JFrame. I've seen plenty of cases where JFrame was extended into, say, MyApplicationFrame. The MyApplicationFrame class doesn't really add any functionality to JFrame. It merely calls some set methods from the constructor, and perhaps launches a main application thread. This is clearly an Inheritance approach, but a misguided one. The same thing could have been accomplished by simply instantiating a JFrame from a "launcher" class, configuring the JFrame by calling its API, starting any threads from the launcher, and then gracefully exiting the launching code and allowing the configured JFrame to live its life.
Some skeptics out there might be saying, "But why is that any better? What's wrong with extending the class if I know I want to configure it the same way every time anyway?" There are a few reasons, but let me respond first by asking, would you extend JTextField just to set its text to something that you happen to always use as a default, such as "No text entered"? I doubt it. Why should you deal with other JComponents any differently?
When you use Inheritance instead of Composition, you end up dragging along a lot of API that, beyond being useless or meaningless in your context, could actually wreak havoc if you haven't accounted for it. You are, in essence, creating another class with all of the same API as the extended class. Is that really what you wanted? Have you thought through the consequences of calling all of those methods on your new class? And do you even want those methods to ever be called at all? Probably not.
Perhaps more importantly, by using Composition your code will become much more flexible and maintainable. Changes in the Swing API will rarely, if ever, impact you. And should you ever want to change to a different GUI toolkit, such as SWT, or even Buoy, you will be able to do so without any impact to the code that is calling your code. You isolate the API. The value of that can hardly be overstated.
