In the past few weeks, we’ve covered design patterns from the Gang of Four (GoF) book “Design Patterns: Elements of Reusable Object-Oriented Software”.
The GoF book is a highly influential book, often seen as the start of the software design patterns movement. This has led many people to believe that the GoF book is the “gospel” of design patterns, and that its 23 patterns are the only “true” patterns.
Nothing can be further from the truth! For whatever reason, the four authors merely happened to document those 23 patterns. Those reasons could have been as simple as publishing deadlines, or a limit on the number of pages, or even that they were tired of writing.
Many patterns exist outside the GoF book. In this article we look at a simple but useful design pattern that isn’t in the GoF book: the Null Object pattern.
Remember that a pattern is a proven solution to a recurring problem in a specific context. As the context changes, so do the patterns. Every context has its own recurring problems and its own elegant solutions.
Patterns should be used as examples of good design. They teach us how to apply important object-oriented principles. They definitely should not be viewed as gospel or dogma. Patterns are tools in software engineering.
The Null Object Pattern
As most Java programmers know, we often get NullPointerException
s when we are developing and testing our code. We generally have to test for null
before performing an operation. This puts an additional burden on the programmer.
The intent of the null object pattern is to minimize null
checking. Instead, we identify the null behaviour and encapsulate it in the type expected by the client code. We define null behaviour, or implement a very simple do-nothing method. This way we no longer need to deal with special handling of null
references. This keeps our code clean and readable. A null object is very predictable and has no side effects because it does nothing.
To encapsulate a null object in the type expected by the client code, we create an abstract class specifying the various operations to be done. We then create concrete classes extending this class. One of these concrete classes will be a null object class providing appropriate do-nothing implementations. This class can be used seamlessly where we need to check for a null
value.
As null objects should not have any state, we don’t need to create multiple identical null objects. This means we can implement null objects as singletons.
The null object pattern is a special case of the strategy pattern, i.e. a strategy of doing nothing.
The Null Object Method: AWT Example
The adapter classes in the java.awt.event
package – ComponentAdapter
, ContainerAdapter
, FocusAdapter
, KeyAdapter
, MouseAdapter
, MouseMotionAdapter
and WindowAdapter
– are not implementations of the Adapter pattern. They are actually Null Objects. It was a poor naming choice by Sun in the early days of Java.
If we implement a listener interface with more than one method in it, we are forced to define all of the methods in it, even if we’re just interested in listening for one event. The abstract XxxAdapter
classes are convenience classes for creating listener objects. They define do-nothing/null methods for all of the defined interface methods, so we only have to override specific methods for the events we’re interested in.
The following code is the implementation of the KeyAdapter
class (minus Javadoc comments):
package java.awt.event; public abstract class KeyAdapter implements KeyListener { public void keyTyped(KeyEvent e) {} public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) {} }
We use the KeyListener
and KeyAdapter
classes as follows:
// empty text field 80 columns wide TextField textField = new TextField(80); // Anonymous inner class using the KeyListener // interface to listen to the keys typed textField.addKeyListener(new KeyListener() { // only override the method we're interested in @Override public void keyPressed (KeyEvent e) {} @Override public void keyReleased(KeyEvent e) {} @Override public void keyTyped (KeyEvent e) { // appropriate code } }); // Anonymous inner class using the KeyAdapter // class to listen to the keys typed. textField.addKeyListener(new KeyAdapter() { // only override the method we're interested in @Override public void keyTyped (KeyEvent e) { // appropriate code } });
We can save writing a lot of boilerplate code when we use the adapter classes. The savings increase when we listen for other events:
- The
ComponentListener
interface has 4 methods. - The
MouseListener
interface has 5 methods. - The
WindowListener
interface has 7 methods.
The MouseAdapter
class implements 8 empty methods; it implements the MouseListener
, MouseMotionListener
and MouseWheelListener
interfaces.
The WindowAdapter
class implements 10 empty methods; it implements the WindowFocusListener
, WindowListener
and WindowStateListener
interfaces.
Even though these adapter classes have all been marked as abstract
, they are actually concrete classes, in that they contain only fully implemented code. They have been marked as abstract
so we cannot instantiate an essentially null object using the new
operator. We are forced to implement some code, even if it’s just an empty class code block:
textField.addKeyListener(new KeyAdapter() {} ); // works! textField.addKeyListener(new KeyAdapter() ); // does not work!
As you can see, the null object is a simple and easy to use pattern, fulfilling a useful role.
The Optional Class
The java.util.Optional
class provides a high-level solution for representing optional values instead of using null
references. The Optional
class is a very comprehensive implementation of the Null Object pattern with all the bells and whistles we can imagine. This will be a topic for a later tip.
What’s Next?
Next week we’ll dive into some of the factory patterns, starting with the (non-GoF) Simple Factory pattern. Stay tuned!
I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.