Null Object Pattern¶
Null Object is OOP pattern often very useful for making code more streamlined and maintainable by avoiding cumbersome code branching.
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