3.9 Null Object
The Null Object pattern replaces null with an object that implements the same interface but does nothing (or returns neutral values). It turns "absence" into a legitimate, callable behavior, eliminating null checks scattered everywhere.
The lab below toggles "real logger / null logger" — notice the client line logger.log(...) stays exactly the same. The null object frees the caller from caring whether a logger exists at all.
Make "nothing" safely callable
interface Logger { void log(String msg); }
class ConsoleLogger implements Logger {
public void log(String msg) { System.out.println(msg); }
}
class NullLogger implements Logger {
public void log(String msg) { /* intentionally do nothing */ }
}
// the factory never returns null
Logger getLogger() {
return enabled ? new ConsoleLogger() : new NullLogger();
}Because NullLogger is also a valid Logger, the client can call logger.log(...) blindly with no if (logger != null). The comparison lab below shows vividly how, once a null object is introduced, the null-check branches at call sites all disappear.
Benefits, boundaries, and modern alternatives
Benefits:
- Eliminate repetitive, easy-to-miss null checks; call code reads more linearly.
- Concentrate "default behavior when absent" in one class instead of every call site.
Boundaries:
- It fits benign defaults where "absent = do nothing." If "absent" should be an error (e.g., a critical config is missing), a null object can silently swallow bugs — there, failing loudly is safer.
Modern alternatives: many languages offer Optional (Java), Maybe, or nullable types + ?. (Kotlin/TS/C#). They converge with Null Object — all systematically handle "the value might not exist."