DEV Community

Avraam Mavridis
Avraam Mavridis

Posted on • Updated on

Null Object Pattern

Many times functions accept object references that can be null, and we tend to add if statements to treat the special case where null is passed to a function, and either provide a default response or do nothing. In the following example our calculateSpeed function expects an object that has a getSpeed function. In Javascript we will have to do something like:

class Car {
  getSpeed(){
    return 100;
  }
}


const calculateSpeed = function(vehicle){
  if(vehicle && typeof vehicle.getSpeed === 'function'){
    return vehicle.getSpeed();
  } else {
    return 50;
  }
}

const car1 = new Car();

console.log(calculateSpeed(car1)); // 100
console.log(calculateSpeed()); // 50

But there is a better way to achieve that. Using the Null Object Pattern we can create a class that acts as vehicle, lets call it DefaultMovable.

class DefaultMovable {
  getSpeed(){
    return 50;
  }
}

Our DefaultMovable class provides the default functionality (aka the else in our previous code snippet), that way we can avoid the if/else statement.

class Car {
  getSpeed(){
    return 100;
  }
}

class DefaultMovable {
  getSpeed(){
    return 50;
  }
}

const calculateSpeed = function(vehicle = new DefaultMovable()){
  return vehicle.getSpeed();
}

const car1 = new Car();

console.log(calculateSpeed(car1)); // 100
console.log(calculateSpeed()); // 50

The UML diagram of this pattern will look like this:

The same example in Ruby would look like:

class Car
  def get_speed
    100
  end
end

class DefaultMovable
  def get_speed
    50
  end
end


def getSpeed(vehicle = DefaultMovable.new)
  vehicle.get_speed();
end

This is just a pattern and as every pattern it has its pros and cons, apply it thoughtfully based on your use case. (The example is fictional for the shake of demonstrating the pattern)

Top comments (16)

Collapse
 
dainbrump profile image
Mark Litchfield • Edited

This seems like bad design to me. Why wouldn't you establish the defaults in the Car class, set a private variable speed in the class and just add a constructor that accepts a speed. Creating a second, unrelated class, just seems to be messy and more prone to the introduction of hard-to-trace bugs. Perhaps this was not the best example to use for demonstrating a Null Object Pattern.

class Car {
  speed = 50;
  constructor(speed) {
    this.speed = speed || this.speed;
  }
  getSpeed() {
    return this.speed;
  }
}

const calculateSpeed = function (vehicle = new Car()) {
  return vehicle.getSpeed();
}

const car1 = new Car(100);

console.log(calculateSpeed(car1)); // 100
console.log(calculateSpeed()); // 50

The code above solves the problem without potentially introducing new ones. Obviously, the constructor could be cleaned up to ensure we've received a number for speed and so on.

Collapse
 
craigbrad profile image
Craig Bradley

Yes, this solves the problem but doesn't really make it clear that we're dealing with null's in the constructor. I feel like the example in the post is not the best because it's just returning a value from the method. If the behaviour was more complex, then you might want to do something specific if the case where we have null. That's where the null object pattern really shines.

Collapse
 
igorlord profile image
Igor Lubashev

The example is flawed. As presented, this is just one big hack. Why is the default value 50? Is it a mistake to call this without a valid argument (one that has the expected function)? If it is a mistake, do not paper over it by returning 50. If it is an expected condition, NullMovable is a horrible name, since it does not describe what concept/abstraction it implements. (Null abstraction is such that it is an error to perform any operation on it.)

Collapse
 
avraammavridis profile image
Avraam Mavridis • Edited

Agree, changed to DefaultMovable . Thx for the feedback

Collapse
 
pdeveloper profile image
PDeveloper

A better example would be in order, since the one presented doesn't make sense - what is the context of calling calculateSpeed without a specific car? For that function, it should specifically enforce that a parameter be passed.

If there is a place in your code where you are okay with using context-free default values, it usually means that it's unnecessary to call that code in the first place, and the real architectural issue lies higher up.

I can see this being useful in specific cases though, where you're dealing with undefined input, and the default object / values are logical to be useful further on.

Collapse
 
craigbrad profile image
Craig Bradley

It would have been nice to see what some of the pro's and con's are of using this pattern.

I've seen a few comments stating that it's bad because there is more code. More code does not mean that the code is cleaner. We could write nested if statements on one line using ternary, but I'm sure you understand why this is a bad idea.

We shouldn't be checking if an object is nil or null as this means we're concerned with types. Ruby in particular is somewhere you would probably see this pattern as it encourages duck typing and polymorphism. The null object pattern is really useful when you're passing objects around the stack and calling methods in multiple places. Everywhere you call a method on an object you would also firstly need to check if the object is nil. Yes, we should never really have null objects, but we always will, as exceptional behaviour will always be present. Systems fail, software fails.

Collapse
 
lewiscowles1986 profile image
Lewis Cowles

Embrace composition of multiple simpler elements rather than null-patterns. You'll still have the boilerplate of your solution, but it'll be clearer (explicit) what the actual state is.

For things like CSV or other simplified flat input, null is just fine. The format is limited so you have to workaround and null makes more sense for some columns than ''.

You wouldn't want a browser having null elements. It'd be better to have a list of children which is either empty, or has N elements sharing an interface, which conform to a rigid specification laid out in the engine. Null isn't needed. The same applies for defaults that are stored, unless auditing is a requirement.

Collapse
 
mindsers profile image
Nathanaël CHERRIER

Do we agree that the default parameter in JavaScript handles the undefined case but not the null one?

Collapse
 
avraammavridis profile image
Avraam Mavridis

Sure, that is just the name of the pattern in literature (unfortunately) en.wikipedia.org/wiki/Null_object_...

Collapse
 
chenge profile image
chenge
class NullObject
  def method_missing(*)
    self
  end
end

This is really null object.

Collapse
 
vasilvestre profile image
Valentin Silvestre

To be honest.. it's dirty :/

Collapse
 
elmuerte profile image
Michiel Hendriks

I wouldn't overload NULL. It already has a clear definition. It would be better to use Void or Nop (no operation) as name.

Collapse
 
_andys8 profile image
Andy

Use Maybe / Optional instead

Collapse
 
itaditya profile image
Aditya Agarwal

I don't like this

Collapse
 
chenge profile image
chenge

Good, simple and clear post, thanks.

Collapse
 
fabiuxx profile image
Fabio González

So ... sentinel values?