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. SlateModelListener
s 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:
// <a href="
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);
}
// <a href="
import java.util.EventListener;
public interface SlateModelListener extends EventListener {
public void slateChanged(SlateModelEvent event);
}
// <a href="
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
:
// <a href="
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:
// <a href="
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:
// <a href="
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:
// <a href="
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:
-
Is it possible to clearly classify one of the classes as the primary class and the other as a supporting class?
- 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.