February 26th 2007
Nil Object Pattern
Or, as it’s called more widely, the null object pattern. Of course, REALbasic calls “null” nil, so I think it’s better to call it the Nil Object Pattern when dealing with REALbasic code.
The Nil Object Pattern is a design pattern that simplifies code by using a default value as the “nil” state. This simplifies code because instead of checking for Nil everywhere, this object sits in place and responds appropriately to any requests (generally ignores them, or returns sensible values).
I was writing code this weekend that I refactored to use this approach. I found myself typing “if foo <> nil then” a little too much, and realized that this pattern would simplify the solution quite well. Another usage situation that I’ve seen is the REALbasic compiler. In the compiler, we use this approach because errors can propagate upstream, but we still want to be able to continue on. By using this approach, we can guarantee we can continue in the compiler and not worry about a null pointer, which in C++ crashes instead of giving you a nice exception
So, how can we use this in REALbasic? I honestly can’t think of any good built-in examples in our framework that could benefit from this sort of approach, so we’re going to have to jump into hypothetical land. Consider the following code:
Interface House
Function HowManyStories() As Integer
Sub AddThing( o As Object )
Function RemoveLastThing() As Object
End Interface
Class SimpleHouse
Implements House
Private mThings() As Object
Function HowManyStories() As Integer
Return 1
End Function
Sub AddThing( o As Object )
mThings.Append o
End Sub
Function RemoveLastThing() As Object
Return mThings.Pop
End Function
// More useful stuff
End Class
Function LocateHouse( someparameters ) As House
// Find the house based on the parameters…
End Function
Now, pretend that LocateHouse was guaranteed to always find the house. All of your code in your program relied upon this basic fact, that LocateHouse simply could not fail. Then the inevitable happened: LocateHouse would make more sense if it [i]could[/i] fail.
So now you’re faced with the dreading realization that all of your code doesn’t check for nil. You have two options: check for nil everywhere you call LocateHouse and figure out how to handle it properly. [i]Or[/i] you could create a NilHouse implementation to return in the failure case.
Class NilHouse
Implements House
Function HowManyStories() As Integer
Return 0
End Function
Sub AddThing( o As Object )
// Do nothing
End Sub
Function RemoveLastThing() As Object
// Do nothing
End Function
End Class
And viola, your code works as always, and you still don’t need to check for nil because sensible implementations are kept in place that keep your other methods simply working as always.
This pattern is a powerful pattern that certainly can be abused. However, in certain circumstances it is the most elegant way to reduce complexity in code.