- A Null Object is an object with defined neutral or null behavior.
- The Null Object design pattern describes the uses of such objects and their behavior (or lack thereof).
- In most object-oriented languages, such as Java or C#, references may be null.
- These references need to be checked to ensure they are not null before invoking any methods, because methods typically cannot be invoked on null references.
- Instead of using a null reference to convey absence of an object, you can create an object which implements the expected interface, but method body is empty.
- The advantage of this approach over a working default implementation is that a Null Object is very predictable and has no side effects: it does nothing.
- For example, a function may retrieve a list of files in a folder and perform some action on each.
- In the case of an empty folder, one response may be to throw an exception or return a null reference rather than a list.
- Thus, the code which expects a list must verify that it in fact has one before continuing, which can complicate the design.
- By returning a null object (i.e. an empty list) instead, there is no need to verify that the return value is in fact a list.
- The calling function may simply iterate the list as normal, effectively doing nothing.
- It is, however, still possible to check whether the return value is a null object (e.g. an empty list) and react differently if desired.
- The null object pattern can also be used to act as a stub for testing, if a certain feature such as a database is not available for testing.
Participants
AbstractObject
(IAutomobile)
- Declares the interface for Client’s collaborator.
- Implements default behavior for the interface common to all classes, as appropriate.
RealObject
(MiniCooper, BMW335XI)
- Defines a concrete subclass of
AbstractObject
whose instances provide useful behavior.
NullObject
(NullAutomobile)
- Provides an interface identical to
AbstractObject
so that a null object can be substituted.
- Implements its interface to do nothing.
- Do nothing depends on what sort of behavior Client is expecting.
- When there is more than one way to do nothing, more than one
NullObject
may be required.
var autoRepository = new AutoRepository();
var automobile = autoRepository.Find("bmw");
if (automobile != AutoRepository.NullAutomobile)
{
automobile.Start();
automobile.Stop();
}
public interface IAutomobile
{
Guid Id { get; }
string Name { get; }
void Start();
void Stop();
}
public class NullAutomobile : IAutomobile
{
public Guid Id => Guid.Empty;
public string Name => string.Empty;
public void Start() { }
public void Stop() { }
}
public class MiniCooper : IAutomobile
{
public Guid Id => Guid.NewGuid();
public string Name => "Mini Cooper S";
public void Start() => Console.WriteLine("Mini Cooper started. 1.6L of raw power is ready to go.");
public void Stop() => Console.WriteLine("Mini Cooper stopped.");
}
public class BMW335XI : IAutomobile
{
public Guid Id => new Guid("68BECCDC-0FBD-4FB9-B0BB-D5D8A2AFD9F8");
public string Name => "BMW 335 Xi";
public void Start() => Console.WriteLine("Beemer started. All 4 wheels under power.");
public void Stop() => Console.WriteLine("Beemer stopped.");
}
public class AutoRepository
{
public static IAutomobile NullAutomobile { get; } = new NullAutomobile();
public IAutomobile Find(string carName)
{
if (carName.Contains("mini"))
return new MiniCooper();
return NullAutomobile;
}
}
Rules of Thumb
- The Null Object class is often implemented as a Singleton.
- Since a null object usually does not have any state, its state can’t change, so multiple instances are identical.
- Rather than use multiple identical instances, the system can just use a single instance repeatedly.
- If some clients expect the null object to do nothing one way and some another, multiple NullObject classes will be required.
- If the do nothing behavior must be customized, the
NullObject
class will require pluggable variables so that the client can specify how the null object should behave.
- This may generally be a symptom of the
AbstractObject
not having a well defined (semantic) interface.
- A Null Object does not transform to become a Real Object.
- If the object may decide to stop providing do nothing behavior and start providing real behavior, it is not a null object.
- It may be a real object with a do nothing mode, such as a controller which can switch in and out of read-only mode.
- If it is a single object which must mutate from a do nothing object to a real one, it should be implemented with the State pattern or perhaps the Proxy pattern.
- In this case a Null State may be used or the proxy may hold a Null Object.
- The use of a null object can be similar to that of a Proxy, but the two patterns have different purposes.
- A proxy provides a level of indirection when accessing a real subject, thus controlling access to the subject.
- A null collaborator does not hide a real object and control access to it, it replaces the real object.
- A proxy may eventually mutate to start acting like a real subject.
- A null object will not mutate to start providing real behavior, it will always provide do nothing behavior.
- A Null Object can be a special case of the Strategy pattern.
- Strategy specifies several ConcreteStrategy classes as different approaches for accomplishing a task.
- If one of those approaches is to consistently do nothing, that
ConcreteStrategy
is a NullObject
.
- For example, a Controller is a View’s Strategy for handling input, and NoController is the Strategy that ignores all input.
- A Null Object can be a special case of the State pattern.
- Normally, each ConcreteState has some do nothing methods because they’re not appropriate for that state.
- In fact, a given method is often implemented to do something useful in most states but to do nothing in at least one state.
- If a particular ConcreteState implements most of its methods to do nothing or at least give null results, it becomes a do nothing state and as such is a null state.
- A Null Object can be used to allow a Visitor to safely visit a hierarchy and handle the null situation.
- Null Object is a concrete collaborator class that acts as the collaborator for a client which needs one.
- The null behavior is not designed to be mixed into an object that needs some do nothing behavior.
- It is designed for a class which delegates to a collaborator all of the behavior that may or may not be do nothing behavior.