Java Tip 63: Avoid 'constructor madness'

Learn when to just say no to constructor overload

With Java, the language design for constructors is quite elegant -- so elegant, in fact, that it's tempting to provide a host of overloaded constructors. When the number of configuration parameters for a component is large, there can be a combinatorial explosion in constructors, ultimately leading to a malady known as constructor madness. As an alternative to implementing constructors to accommodate all possible start-up configurations, this tip illustrates the use of a supporting configuration class.

To demonstrate the design, we'll implement a progress bar widget with several configuration parameters and/or properties. The Progress component supports:

  • Horizontal or vertical layout
  • Multiple "directions of progress"
  • Percent-versus-no-percent text display
  • Width and height
  • Margin size
  • Foreground, background, and text color

Accommodating a variety of initialization possibilities

Traditionally, there are two ways to communicate initialization data to an instance:

  1. Pass the data as arguments to a constructor
  2. Provide access methods for post-hoc initialization

There is no suggestion here to limit access methods. Access methods are a basic design component for object-oriented programming. In the case of instance creation, however, post-hoc initialization can be cumbersome. For this reason, it's common to provide a variety of constructors.

Of course, it's possible to provide one monster constructor with a parameter for each configuration option. In general, providing this type of reference constructor is a good idea. Given the definition of a reference constructor, other convenience constructors with fewer parameters can then invoke the reference constructor by calling this() and filling in default values:

    public Progress(int width, int height, int margin,
            Orientation orientation, DisplayPercent display_percent,
                Color bg, Color fg, Color text) {
    ...
    }   /* Progress */
    public Progress(int width, int height, Color fg) {
        this(width, height, DEFAULT_MARGIN,
            new Orientation(), new DisplayPercent(),
            null, fg, DEFAULT_TEXT);
    }   /* Progress */

With eight configurations options, however, the number of convenience constructors necessary to support common configuration scenarios is large. The issue is not the difficulty in coding the constructors, but the difficulty, for the user, in having continually to refer to the API to pick one of many constructors with varying signatures.

One solution is to provide a supporting configuration class for each primary class that requires an unwieldy number of configuration options. For example, the Progress widget is accompanied by ConfigureProgress. The basic idea is to first create a nongraphical class with a public instance variable for each configuration option, and then provide a constructor for the primary class that takes a configuration instance as an argument:

public final class ConfigureProgress {
    public int width, height, margin;
    public Orientation orientation;
    public DisplayPercent display_percent;
    public Color bg, fg, text;
    ConfigureProgress()
    {
        width = Progress.DEFAULT_WIDTH;
        height = Progress.DEFAULT_HEIGHT;
        margin = Progress.DEFAULT_MARGIN;
        orientation = new Orientation();
        display_percent = new DisplayPercent();
        bg = null;
        fg = null;
        text = Progress.DEFAULT_TEXT;
    }   /* ConfigureProgress */
}   /* ConfigureProgress class */

Note that each field is initialized with default values of

Progress

for each configuration option.

Then, the Progress component provides a constructor with a single parameter representing an instance of ConfigureProgress:

    public Progress(ConfigureProgress cp) {
        this(cp.width, cp.height, cp.margin,
            cp.orientation, cp.display_percent,
            cp.bg, cp.fg, cp.text);
    }   /* Progress */

Last, when instantiating a Progress widget, a programmer can modify the relevant configuration options directly in the ConfigureProgress instance before passing it to the constructor for Progress:

    ConfigureProgress cp = new ConfigureProgress();
    cp.width = 40;
    cp.height = 150;
    cp.bg = Color.red;
    cp.fg = Color.cyan;
    p4 = new Progress(cp);

In this case, the programmer has to know the names of eight instance variables, but doesn't have to remember or look up a large number of constructor signatures.

Exercising restraint

This type of pairing of primary and secondary configuration classes is not generally recommended because, in most cases, the set of constructors for a class is small. Only when the set of constructors becomes cumbersome does the additional complication of having a secondary configuration class becomes a better alternative.

The progress widget: Appearance

Minimally, to test Progress's display operations, four instances are necessary -- one for each orrientation. The test applet, ProgressMultiTest, instantiates four progress bar instances and "drives" them from separate threads of execution:

If you have a JDK 1.1-compatible browser, you can run the ProgressMultiTest applet directly. (This applet is accompanied by several descriptive paragraphs, so you must scroll down to find the applet on its HTML page.)

The progress widget: Design

The Progress component is primarily an "internal" widget. That is, Progress, like AWT's Label component, does not provide a three-dimensional look and feel. It is primarily designed for display within areas that are strictly controlled by the parent (container), for example, the area where the progress bar appears at the bottom of a Web browser.

The Progress component extends Panel in order to take advantage of the insets capability for containers. Progress manages a Canvas-derived drawing area as its only child. Thus, the margin area is easily implemented by controlling the insets values for the enclosing panel.

Normally, a Progress instance is configured with no margin, for example, when located at the bottom of a browser's main window. In these circumstances it's common to set the Progress component's background color to match that of its container, like the second progress widget in the previous screenshot.

Conclusion

With this brief example, you should see the value of providing a secondary class to configure initialization parameters when the quantity of class properties is relatively large. Without the secondary class, you would need to design a cumbersome set of constructors, making the API for your class more complex. The resulting design with the secondary class should be more maintainable in the long-run and simpler for the user of your classes.

Note to our readers: Have you come up with any Java tips and tricks you think may be of value to your fellow JavaWorld readers? If so, send them to javatips@javaworld.com, and you may see your name "in lights" as the author of a future Java Tip!

Learn more about this topic

  • The source code for this Java Tip is available at http://www.javaworld.com/javatips/javatip63/javatip63.zip
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more