2926. Design Pattern - Null Object
Null Object


Behavioral Pattern: Null Object Pattern.

1. Null Object Pattern

The Null Object pattern is used to encapsulate the absence of an object by providing a substitutable alternative that offers suitable default do nothing behavior. In short, a design where “nothing will come of nothing”.

Use the Null Object pattern when:

  • an object requires a collaborator. The Null Object pattern does not introduce this collaboration–it makes use of a collaboration that already exists
  • some collaborator instances should do nothing
  • you want to abstract the handling of null away from the client

2. Example of Implementation

2.1 Interface

public interface Shape { double area(); double perimeter(); void draw(); } 

2.2 Classes

public class Circle implements Shape { private final double radius; public Circle (double radius) { this.radius = radius; } @Override public double area() { // Area = πr^2 return Math.PI * Math.pow(radius, 2); } @Override public double perimeter() { // Perimeter = 2πr return 2 * Math.PI * radius; } @Override public void draw() { System.out.println("Drawing Circle with area: " + area() + " and perimeter: " + perimeter()); } } public class Rectangle implements Shape { private final double width; private final double height; public Rectangle (double width, double height) { this.width = width; this.height = height; } @Override public double area() { // A = w * h return width * height; } @Override public double perimeter() { // P = 2(w + h) return 2 * (width + height); } @Override public void draw() { System.out.println("Drawing Rectangle with area: " + area() + " and perimeter: " + perimeter()); } } public class Triangle implements Shape { private final double a; private final double b; private final double c; public Triangle (double a, double b, double c) { this.a = a; this.b = b; this.c = c; } @Override public double area() { // Using Heron's formula: // Area = SquareRoot(s * (s - a) * (s - b) * (s - c)) // where s = (a + b + c) / 2, or 1/2 of the perimeter of the triangle double s = (a + b + c) / 2; return Math.sqrt(s * (s - a) * (s - b) * (s - c)); } @Override public double perimeter() { // P = a + b + c return a + b + c; } @Override public void draw() { System.out.println("Drawing Triangle with area: " + area() + " and perimeter: " + perimeter()); } } 

2.3 Problematic Usage

public class ShapeFactory { public static Shape createShape(String shapeType) { Shape shape = null; if ("Circle".equalsIgnoreCase(shapeType)) { shape = new Circle(3); } else if ("Rectangle".equalsIgnoreCase(shapeType)) { shape = new Rectangle(2, 4); } else if ("Triangle".equalsIgnoreCase(shapeType)) { shape = new Triangle(3, 4, 5); } // else return null return shape; } } 

When client using this factory to get shape instance, null-check is required if it returns null object. Otherwise, NullPointerException occurs.

public class ShapeProcessor { String[] shapeTypes = new String[] { "Circle", "Triangle", "Rectangle", null}; public ShapeProcessor () { } public void process() { for (String shapeType : shapeTypes) { Shape shape = ShapeFactory.createShape(shapeType); if (shape != null) { // null-check is required if factory returns null object System.out.println("Shape area: " + shape.area()); System.out.println("Shape Perimeter: " + shape.perimeter()); shape.draw(); System.out.println(); } } } } 

2.4 Implementation with NullObject Pattern

Create ‘null’ class as default shape.

public class NullShape implements Shape { public NullShape () {} @Override public double area() { return 0.0d; } @Override public double perimeter() { return 0.0d; } @Override public void draw() { System.out.println("Null object can't be drawn"); } } 

Factory can now return the null object.

public class ShapeFactory { public static Shape createShape(String shapeType) { Shape shape = null; if ("Circle".equalsIgnoreCase(shapeType)) { shape = new Circle(3); } else if ("Rectangle".equalsIgnoreCase(shapeType)) { shape = new Rectangle(2, 4); } else if ("Triangle".equalsIgnoreCase(shapeType)) { shape = new Triangle(3, 4, 5); } else { shape = new NullShape(); } return shape; } } 

Now, client doesn’t need the null check.

public class ShapeProcessor { String[] shapeTypes = new String[] { "Circle", "Triangle", "Rectangle", null}; public ShapeProcessor () { } public void process() { for (String shapeType : shapeTypes) { Shape shape = ShapeFactory.createShape(shapeType); // no null-check required since shape factory always creates shape objects System.out.println("Shape area: " + shape.area()); System.out.println("Shape Perimeter: " + shape.perimeter()); shape.draw(); System.out.println(); } } } 

Output.

Shape area: 28.274333882308138 Shape Perimeter: 18.84955592153876 Drawing Circle with area: 28.274333882308138 and perimeter: 18.84955592153876 Shape area: 6.0 Shape Perimeter: 12.0 Drawing Triangle with area: 6.0 and perimeter: 12.0 Shape area: 8.0 Shape Perimeter: 12.0 Drawing Rectangle with area: 8.0 and perimeter: 12.0 Shape area: 0.0 Shape Perimeter: 0.0 Null object cant be drawn 

3. Source Files

4. References