Java Tip 75: Use nested classes for better organization

Express coupling between components in Java subsystems with top-level nested classes

A typical subsystem in a Java application consists of a set of collaborating classes and interfaces, each performing a specific role. Some of these classes and interfaces are meaningful only in the context of other classes or interfaces.

Designing context-dependent classes as top-level nested classes (nested classes, for short) enclosed by the context-serving class makes this dependency clearer. Furthermore, the use of nested classes makes the collaboration easier to recognize, avoids namespace pollution, and reduces the number of source files.

(The complete source code for this tip can be downloaded in zip format from the Resources section.)

Nested classes vs. inner classes

Nested classes are simply static inner classes. The difference between nested classes and inner classes is the same as the difference between static and nonstatic members of a class: nested classes are associated with the enclosing class itself, whereas inner classes are associated with an object of the enclosing class.

Because of this, inner class objects require an object of the enclosing class, while nested class objects do not. Nested classes, therefore, behave just like top-level classes, using the enclosing class to provide a package-like organization. In addition, nested classes have access to all members of the enclosing class.

Motivation

Consider a typical Java subsystem, for example a Swing component, using the Model-View-Controller (MVC) design pattern. Event objects encapsulate change notifications from the model. Views register interest in various events by adding listeners to the underlying model of the component. The model notifies its viewers of changes in its own state by delivering these event objects to its registered listeners. Often, these listener and event types are specific to the model type, and therefore make sense only in the context of the model type. Because each of these listener and event types must be publicly accessible, each must be in its own source file. In this situation, unless some coding convention is used, the coupling between these types is difficult to recognize. Of course, one may use a separate package for each group to show the coupling, but this results in a large number of packages.

If we implement listener and event types as nested types of the model interface, we make the coupling obvious. We can use any access modifier desired with these nested types, including public. In addition, as nested types use the enclosing interface as a namespace, the rest of the system refers to them as <Enclosing>.<Nested>, avoiding namespace pollution inside that package. The source file for the model interface has all the supporting types, which makes development and maintenance easier.

Before: An example without nested classes

As an example, we develop a simple component, Slate, whose task is to draw shapes. Just like Swing components, we use the MVC design pattern. The model, SlateModel, serves as a repository for shapes. SlateModelListeners subscribe to the changes in the model. The model notifies its listeners by sending events of type SlateModelEvent. In this example, we need three source files, one for each class:

// SlateModel.java
import java.awt.Shape;
 
public interface SlateModel { 
    // Listener management 
    public void addSlateModelListener(SlateModelListener l); 
    public void removeSlateModelListener(SlateModelListener l); 
    // Shape repository management, views need notification 
    public void addShape(Shape s); 
    public void removeShape(Shape s); 
    public void removeAllShapes(); 
    // Shape repository read-only operations 
    public int getShapeCount(); 
    public Shape getShapeAtIndex(int index); 
}
// SlateModelListener.java
import java.util.EventListener;
public interface SlateModelListener extends EventListener { 
    public void slateChanged(SlateModelEvent event); 
} 
// SlateModelEvent.java
import java.util.EventObject;
public class SlateModelEvent extends EventObject { 
    public SlateModelEvent(SlateModel model) { 
        super(model); 
    }
}

(The source code for DefaultSlateModel, the default implementation for this model, is in the file before/DefaultSlateModel.java.)

Next, we turn our attention to Slate, a view for this model, which forwards its painting task to the UI delegate, SlateUI:

// Slate.java
import javax.swing.JComponent;
public class Slate extends JComponent implements SlateModelListener { 
    private SlateModel _model; 
    public Slate(SlateModel model) { 
        _model = model; 
        _model.addSlateModelListener(this); 
        setOpaque(true); 
        setUI(new SlateUI()); 
    } 
    public Slate() { 
        this(new DefaultSlateModel()); 
    } 
    public SlateModel getModel() { 
        return _model; 
    } 
    // Listener implementation 
    public void slateChanged(SlateModelEvent event) { 
        repaint(); 
    } 
} 

Finally, SlateUI, the visual GUI component:

// SlateUI.java
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
public class SlateUI extends ComponentUI { 
    public void paint(Graphics g, JComponent c) { 
        SlateModel model = ((Slate)c).getModel(); 
        g.setColor(c.getForeground()); 
        
        Graphics2D g2D = (Graphics2D)g;
        for (int size = model.getShapeCount(), i = 0; i < size; i++) { 
            g2D.draw(model.getShapeAtIndex(i)); 
        }
    }
} 

After: A modified example using nested classes

The class structure in the example above does not show the relationship between the classes. To mitigate this, we've used a naming convention that requires all related classes to have a common prefix, but it would be clearer to show the relationship in code. Furthermore, developers and maintainers of these classes must manage three files: for SlateModel, for SlateEvent, and for SlateListener, to implement one concept. The same is true with managing the two files for Slate and SlateUI.

We can improve things by making SlateModelListener and SlateModelEvent nested types of the SlateModel interface. Because these nested types are inside an interface, they're implicitly static. Nonetheless, we've used an explicit static declaration to help the maintenance programmer.

Client code will refer to them as SlateModel.SlateModelListener and SlateModel.SlateModelEvent, but this is redundant and unnecessarily long. We remove the prefix SlateModel from the nested classes. With this change, client code will refer to them as SlateModel.Listener and SlateModel.Event. This is short and clear and does not depend on coding standards.

For SlateUI, we do the same thing -- we make it a nested class of Slate and change its name to UI. Because it's a nested class inside a class (and not inside an interface), we must use an explicit static modifier.

With these changes, we need only one file for the model-related classes and one more for the view-related classes. The SlateModel code now becomes:

// SlateModel.java
import java.awt.Shape;
import java.util.EventListener;
import java.util.EventObject;
public interface SlateModel { 
    // Listener management 
    public void addSlateModelListener(SlateModel.Listener l); 
    public void removeSlateModelListener(SlateModel.Listener l); 
    // Shape repository management, views need notification 
    public void addShape(Shape s); 
    public void removeShape(Shape s); 
    public void removeAllShapes(); 
    // Shape repository read-only operations 
    public int getShapeCount(); 
    public Shape getShapeAtIndex(int index); 
    // Related top-level nested classes and interfaces 
    public interface Listener extends EventListener { 
        public void slateChanged(SlateModel.Event event); 
    }
    public class Event extends EventObject { 
        public Event(SlateModel model) { 
            super(model); 
        }
    } 
} 

And the code for Slate is changed to:

// Slate.java
import java.awt.*;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
public class Slate extends JComponent implements SlateModel.Listener { 
    public Slate(SlateModel model) { 
        _model = model; 
        _model.addSlateModelListener(this); 
        setOpaque(true); 
        setUI(new Slate.UI()); 
    } 
    public Slate() { 
        this(new DefaultSlateModel()); 
    } 
    public SlateModel getModel() { 
        return _model; 
    } 
    // Listener implementation 
    public void slateChanged(SlateModel.Event event) { 
        repaint(); 
    } 
    public static class UI extends ComponentUI { 
        public void paint(Graphics g, JComponent c) { 
            SlateModel model = ((Slate)c).getModel(); 
            g.setColor(c.getForeground()); 
            Graphics2D g2D = (Graphics2D)g;
            for (int size = model.getShapeCount(), i = 0; i < size; i++) { 
                g2D.draw(model.getShapeAtIndex(i)); 
            }
        } 
    } 
} 

(The source code for the default implementation for the changed model, DefaultSlateModel, is in the file after/DefaultSlateModel.java.)

Within the SlateModel class, it is unnecessary to use fully qualified names for nested classes and interfaces. For example, just Listener would suffice in place of SlateModel.Listener. However, using fully qualified names helps developers who are copying method signatures from the interface and pasting them into implementing classes.

The JFC and use of nested classes

The JFC library uses nested classes in certain cases. For example, class BasicBorders in package javax.swing.plaf.basic defines several nested classes such as BasicBorders.ButtonBorder. In this case, class BasicBorders has no other members and simply acts as a package. Using a separate package instead would have been equally effective, if not more appropriate. This is a different use than the one presented in this article.

Using this tip's approach in JFC design would affect the organization of listener and event types related to model types. For example, javax.swing.event.TableModelListener and javax.swing.event.TableModelEvent would be implemented respectively as a nested interface and a nested class inside javax.swing.table.TableModel.

This change, together with shortening the names, would result in a listener interface named javax.swing.table.TableModel.Listener and an event class named javax.swing.table.TableModel.Event. TableModel would then be fully self-contained with all the necessary support classes and interfaces rather than having need of support classes and interface spread out over three files and two packages.

Guidelines for using nested classes

As with any other pattern, judicious use of nested classes results in design that is simpler and more easily understood than traditional package organization. However, incorrect usage leads to unnecessary coupling, which makes the role of nested classes unclear.

Note that in the nested example above, we make use of nested types only for types that cannot stand without context of enclosing type. We do not, for example, make SlateModel a nested interface of Slate because there may be other view types using the same model.

Given any two classes, apply the following guidelines to decide if you should use nested classes. Use nested classes to organize your classes only if the answer to both questions below is yes:

  1. Is it possible to clearly classify one of the classes as the primary class and the other as a supporting class?

  2. Is the supporting class meaningless if the primary class is removed from the subsystem?

Conclusion

The pattern of using nested classes couples the related types tightly. It avoids namespace pollution by using the enclosing type as namespace. It results in fewer source files, without losing the ability to publicly expose supporting types.

As with any other pattern, use this pattern judiciously. In particular, ensure that nested types are truly related and have no meaning without the context of the enclosing type. Correct usage of the pattern doesn't increase coupling, but merely clarifies the existent coupling.

Ramnivas Laddad is a Sun Certified Architect of Java Technology (Java 2). He has a Masters degree in electrical engineering with a specialization in communication engineering. He has six years of experience designing and developing several software projects involving GUI, networking, and distributed systems. He has developed object-oriented software systems in Java for the last two years and in C++ for the last five years. Ramnivas currently works at Real-Time Innovations Inc. as a software engineer. At RTI, he is presently working to design and develop ControlShell, the component-based programming framework for building complex real-time systems.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies