128
behavioral

Null Object

Reference Wikipedia ↗
Null Object — class diagram
Plate 128 class diagram

The Null Object pattern provides a substitute for a missing or undefined object. Instead of returning null or throwing an exception when an object is not available, a null object is returned. This null object implements the expected interface without any concrete behavior, effectively doing nothing. This simplifies client code by eliminating the need to constantly check for null values.

The primary goal of the Null Object pattern is to reduce conditional logic and make code more readable. It enables you to treat missing objects as valid objects, allowing you to call methods on them without concern for NullPointerExceptions or similar errors. This pattern is particularly useful when dealing with optional relationships or default behaviors.

Usage

The Null Object pattern is widely used in scenarios where:

  • Optional Dependencies: A component might rely on other components that are not always present. Using a null object allows the component to function even if a dependency is missing.
  • Default Behavior: When an object’s state or properties are initially unavailable, a null object can provide default, no-op behavior.
  • Data Processing Pipelines: In pipelines processing data, missing data points can be represented by null objects instead of halting the process.
  • GUI applications: Representing missing or invalid UI elements.

Examples

  1. Java’s Logging Frameworks (Log4j, SLF4J): Logging frameworks often provide a NullLogger or similar concept. If a logger isn’t explicitly configured for a particular class, the framework might return a null logger that discards log messages. This prevents errors if a client tries to log something when a specific logger is unavailable.

  2. Python’s unittest module: The unittest module provides object as a base class for test cases. In certain scenarios, a default test suite or test runner might be requested. Rather than return None, a minimal, no-op test suite represented as an instance of object is returned, allowing the test framework to continue execution without errors.

  3. JavaScript’s Optional Chaining: While not a direct implementation of the pattern, optional chaining (?.) in JavaScript achieves a similar effect. If a property access chain leads to a null or undefined value, the expression short-circuits and returns undefined instead of throwing an error. This can be considered a language-level abstraction built on the principles of the Null Object pattern.

Specimens

15 implementations
Specimen 128.01 Dart View specimen ↗

The Null Object pattern provides a substitute for a null reference or null value. Instead of checking for nulls everywhere, you call a null object, which responds to all methods in a way that doesn’t affect the program’s logic. This simplifies code and avoids NullPointerExceptions.

The Dart example defines an Animal interface with a speak() method. A concrete Dog class implements this interface. NullDog is the null object, also implementing Animal, but providing a no-op speak() method. The getAnimal() function demonstrates how to return a NullDog instance when a real animal isn’t available, allowing the calling code to treat both real and null animals uniformly. This approach is idiomatic Dart as it leverages interfaces and classes for type safety and promotes a more fluent coding style by reducing null checks.

// Define the interface
abstract class Animal {
  void speak();
}

// Concrete implementation
class Dog implements Animal {
  final String name;

  Dog(this.name);

  @override
  void speak() {
    print('Woof! My name is $name.');
  }
}

// Null Object implementation
class NullDog implements Animal {
  @override
  void speak() {
    // Do nothing - this is the null behavior
  }
}

// Function to get an animal (potentially null)
Animal? getAnimal(bool hasDog) {
  if (hasDog) {
    return Dog('Buddy');
  } else {
    return NullDog();
  }
}

void main() {
  Animal? animal1 = getAnimal(true);
  animal1?.speak(); // Output: Woof! My name is Buddy.

  Animal? animal2 = getAnimal(false);
  animal2.speak(); // No output, no error.
}