Problem Statement
When a system expects an object but receives a null or None value, it often leads to errors or requires many null checks scattered throughout the code. This makes the code complex, error-prone, and harder to maintain.
This diagram shows the client code interacting with an abstract object interface, which can be either a real object or a null object that safely handles calls without side effects.
### Before Null Object pattern (with many null checks): class Animal: def make_sound(self): pass class Dog(Animal): def make_sound(self): print("Woof") class Cat(Animal): def make_sound(self): print("Meow") def animal_sound(animal): if animal is not None: animal.make_sound() else: print("No animal to make sound") # Usage animal_sound(Dog()) # Woof animal_sound(None) # No animal to make sound ### After applying Null Object pattern: class Animal: def make_sound(self): pass class Dog(Animal): def make_sound(self): print("Woof") class Cat(Animal): def make_sound(self): print("Meow") class NullAnimal(Animal): def make_sound(self): # Do nothing or print neutral message print("No animal present") def animal_sound(animal): animal.make_sound() # No null check needed # Usage animal_sound(Dog()) # Woof animal_sound(NullAnimal()) # No animal present