Null Object Pattern

Null Object is OOP pattern often very useful for making code more streamlined and maintainable by avoiding null checks.

Null object is typically (always?) used in conjunction with either a dependency injection pattern where null object figures as an input value, or with factory pattern where the null object figures as an output value.

Practical Introduction

I'll use an example of null object as an input value. For example as an output value you may check out Alexey Kuznetsov's Null Object Pattern in Swift

Let us have a function do_stuff that does something and prints informational messages to standard output. Omitting the actual functionality of do_stuff because its not important, we get a model example:

def do_stuff():
    stdout.write("foo\n")

In some cases we do not want to just do stuff and not print any messages. Most obvious solution would be using a boolean flag (var. 1):

def do_stuff(print_info=True):
    """
    :type  print_info: bool
    :param print_info: if True (default), print additional information about do_stuff progress.
    """
    if print_info:
          stdout.write("foo\n")

This would work correctly. However this increases cyclomatic complexity of do_stuff which means we need to double the test cases to account for the possibility of each test case being run with print_info being either True or False.

More elegant solution is to use a null object for the special circumstance where we do not want any messages printed:

def do_stuff(stdout=sys.stdout):
    """
    :type stdout: file-like
    """
    stdout.write("foo\n")

with usage like:

null=open("/dev/null", "w")
do_stuff(null)

With such simple change we decrease the cyclomatic complexity back to the complexity of original do_stuff. Effectively making the special case behavior (print_info=False) implementation identical to the standard behavior with regard to do_stuff implementation. Meaning we do not need additional test cases for do_stuff.

In this example, the value of null variable is the null object. In this case we leverage a feature of unix like operating systems (man 4 null) because it fits the problem of (not) writing onto standard output. However, in general there is no need for operating system support to use null pattern.

Implementation without OS support could look like:

class NullStdout:
    def write(data):
        return

null = NullStdout()
do_stuff(null)

We have NullStdout class implementing the interface of file-like object (therefore being usable instead of the standard stdout) but with a behavior that is a no-op.

Since functions are first-class objects in python, note a function may be a null object as well. And since we needed to call only a single method on stdout, our do_stuff implementation may also look like (var. 2):

def do_stuff(write=sys.stdout.write):
    write("foo\n")

with use case like:

def nullwrite(data):
    return

do_stuff(nullwrite)
ctime

Nov 4, 2019