Home > .NET, Design Patterns, Visual Basic > The null object pattern in use

The null object pattern in use

What is the null object pattern and why should you care? Well, if you’re a masochist who likes to add superfluous validation code to any give project or if you are an obsesive compulsive about adding such superfluous code… then I suppose you wouldn’t care. However, for the rest of us this can be a quite useful design pattern that can lead to simpler code that is more maintaianable and better testable. That covers the why, so here is the what in a nutshell.

Design patterns provide an architectural or algorithmic set of steps or procedures to solve specific programming design issues. This design pattern addreses the common issue with .NET development where an instance of a class, or a reference object, is tested for having a null reference before using the object in code. Why would you do this in the first place? Consider that if you attempt to access a method or a property on a reference object that is null (or Nothing in VB.NET) you will encounter a nasty NullReferenceException. This is not the type of error you would want to introduce to your user, but even if you are savvy enough to catch such an error before it crashes your application you are still posed with the issue of restoring the normal flow of operation for that application in light of this unexpected error. Here is a simple example of how things can go wrong:

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
       'You really should never do this.
       DeleteEmployee(Nothing)
    End Sub

    Private Sub DeleteEmployee(emp As Employee)
        'We can just trust this emp variable is good, right?
        emp.Delete()
    End Sub

The potential problem here can be easily mitigated by some simple checks as such…

    Private Sub DeleteEmployee(emp As Employee)

        'We can't trust this value, even when called from our own code.
        If emp IsNot Nothing Then
            emp.Delete()
        Else
            'Ignore, passively log, or throw an exception...  Your call.
        End If

    End Sub

What the null object pattern attempts to do is avoid the need for null reference checks by essentially providing a “placeholder” instance of the class that can be used as a default object in lieu of null. However, it should be pointed out that this does not remove all responsibility from the developer — he or she can still make a null reference. This pattern merely encourages the developer to use the provided benefits of the implementation. So the onus is on the developer to do the right thing (which sounds like a good rule of thumb anyhow, right?) What are the qualities of an object that adhere to the null object pattern?

  • Provides a placeholder reference
  • Never represents a real entity or set of values
  • Offers no functionality through methods or functions
  • Has property values that are defaulted in a way to negate functionality

In many ways this is considered the most extreme version of a class object (from a minimalist perspective), but that is precisely what is required if you were to offer an instance of a class that is otherwise capable of performing operations like, say, altering live data or communicating with a serial port or providing the user with critical information.

So we’ve established that null-checking is important, now let’s explore how the null object pattern facilitates this common task Consider our simple Employee class before implementing this pattern. It has a few properties and methods that will either perform or initiate some data source operations.

Public Class Employee

    Private _ID As Int32

    Public ReadOnly Property ID As Int32
        Get
            Return _ID
        End Get
    End Property

    Public Property FirstName As String
    Public Property LastName As String

    Public Sub Add()
        '... logic that results in a new employee here
    End Sub

    Public Sub Delete()
        '... logic that results in deleting an employee record here
    End Sub

    Public Sub Save()
        '... logic that results in updating an employee record here
    End Sub
End Class
 

Remember that the goal of the pattern is to provide a single instance that serves as a placeholder for a real instance. The simplest way to accomplish this goal is to add some members that are shared in scope, meaning they belong to the class definition itself and not to any one instance. Shared members are accessible through the class name. Instance methods and properties can access shared members but the reciprocal is not allowed. By managing this shared instance we can ensure that only one is ever created over the life of the application and that it is available through easy means via a readonly property or a function. I prefer to use the model set forth with the String class and its Empty function. Here is my implementation of Empty for our Employee class where our shared instance is accessed through a function called Empty.

Public Class Employee

#Region " Shared Functionality "

    Private Shared _Empty As Employee

    Public Shared Function Empty() As Employee
        '** NOTE:  THIS IS NOT THREAD-SAFE CODE!

        If _Empty Is Nothing Then
            _Empty = New Employee() With {.ID = 0,
                                          .FirstName = String.Empty,
                                          .LastName = String.Empty}
        End If

        Return _Empty

    End Function

    Public ReadOnly Property IsEmpty As Boolean
        Get
            'Determine which factors define a non-existant record.
            'Just ensure this can never occur naturally.
            Return _ID = 0
        End Get
    End Property

#End Region

    Private _ID As Int32

    Public ReadOnly Property ID As Int32
        Get
            Return _ID
        End Get
    End Property

    Public Property FirstName As String
    Public Property LastName As String

    '... other code below

End Class

I like to add an extra helper by way of a read-only property called IsEmpty that can quickly allow calling code to assess if this object has functionality or not. I do not want that code to “guess” what empty means (i.e. if ID = 0) because those rules could change over time or be applied inconsitently. Now whenever we instantiate an employee variable we can explicitly set it to this shared “empty” employee.

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click

        Dim emp As Employee = Employee.Empty
        'TODO:  write code to get a real employee (need to get to this soon!)
        DeleteEmployee(emp)

    End Sub

    Private Sub DeleteEmployee(emp As Employee)

        'Now we *should* trust this employee object.
        'I wouldn't blame you if you still want to be paranoid
        'and check for null though 😉

        emp.Delete()

    End Sub

Great! So the job is done now, right? Well, not exactly. What happens when we call Delete() on this object? Yeah, I don’t know either yet – that’s why we are not done. Maybe the database will throw an error if a record with ID zero is updated. Maybe something worse could happen like bad or corrupted data. So couldn’t we just do this?

        If Not emp.IsEmpty Then
            emp.Delete()
        End If

Sure… but if we leave it up to the calling code to conditionally handle the case of an empty instance then this is not much better than performing a null check. In an effort to completely neuter this object we need to put the onus on the class itself to handle an empty instance correctly during all functionality. That way we can just implicitly trust that a call to Delete will work itself out correctly no matter what type of employee object we have. Here’s one way to accomplish this, looking back at our data operation methods on Employee:

    Public Sub Add()
        If Not IsEmpty Then
            '... logic to insert a new employee here
        End If
    End Sub

    Public Sub Delete()
        If Not IsEmpty Then
            '... logic to delete an employee record here
        End If
    End Sub

    Public Sub Save()
        If Not IsEmpty Then
            '... logic to update an employee record here
        End If
    End Sub

Now we have a class that is much more in keeping with the spirit of the null object pattern. But if we want to be even more explicit in our protections there is one more thing we can do. We could disallow the settable properties to receive real world values, essentially how to block our empty employee from becoming “Joe Smith”.

    Private _FirstName As String
    Private _LastName As String

    Public Property FirstName As String
        Get
            Return _FirstName
        End Get
        Set(value As String)
            If Not IsEmpty Then
                _FirstName = value
            End If
        End Set
    End Property

    Public Property LastName As String
        Get
            Return _LastName
        End Get
        Set(value As String)
            If Not IsEmpty Then
                _LastName = value
            End If
        End Set
    End Property

First we have to break up the succinct one-line property declarations (a VS2010 nicety) in order to insert setter code. But here we just simply add an empty check that blocks setting while empty.

Some may consider implementing this null object pattern an unnecessary burden to the code and that’s fine. You should only code to your comfort level (or that of the person who’s paying you). But what we have accomplished with this last bit of code is to fully negate the functionlity of the empty object, which was the stated goal of the pattern. Also, please keep in mind that there are other ways that this pattern can be implemented – this is just one way that I have done it for one common scenario.

Now, cash in on all the countless hours of superfluous null-checking code you’ll be saving over the span of your programming career by kicking back and cracking open a beer. You will have earned it! 🙂

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: