Null Object And Refactoring

When refactoring a design that uses the NullObject pattern, one must beware of refactorings that change the system to use NullObject classes to represent domain concepts, rather than being implementation details. This goes against the intent of the NullObject pattern and will cause headaches at later date.


Here's an example from my own experience with the SceneBeans framework.

The SceneBeans framework provides a SceneGraph data structure that defines a graphical scene as a directed graph of polymorphic scene-graph "nodes" that are processed using the VisitorPattern. We used the NullObject pattern to mark the edges of the SceneGraph, and combined the NullObjectAndVisitor patterns so that the NullObject nodes did not DoubleDispatch to the visitor -- as far as the visitors knew, the NullObject nodes did not even exist.

One of the scene graph classes was a "switch" node that would contain multiple subgraphs but only draw one at a time. We later needed to selectively show or hide parts of the scene. We realised that we could do this by putting a subgraph and a NullObject node into to a switch node -- switching between the subgraph and the NullObject would have the effect of showing or hiding the subgraph. This only required changing our file format so that files could explicitly create null objects in the graph. Cunning!

Or so we thought... We noticed a problem when we wrote a visitor to write a scene graph into a file in our format. The visitor never wrote the NullObjects because they did not double dispatch to it. This was fine for NullObjects at the edge of the graph, but was the wrong behaviour for NullObjects that had been explicitly included in "switch" nodes.

The problem was that our refactoring had changed the way that NullObjects were used from an internal implementation detail of how the framework marked the edges of the scene graph to explicitly representing concept of "draw nothing" in a user-visible way.

Solutions...

  1. Add a visitNull method to the visitor interface, and provide a do-nothing default implementation in the abstract base class from which all visitors are derived. (We chose this one)
  2. Use different classes for Null Objects that mark the edge of the graph from those used to represent "draw nothing". (The most elegant approach, but adds classes to the system)
  3. Explicitly check for NullObjects in the save-to-file visitor. (A quick-and-dirty fix that we ruled out for obvious reasons)

--NatPryce

Is solution #2 unacceptable because of FearOfAddingClasses?

Solution #2 is not unacceptable at all. I would say that it's the ideal solution in most cases. However, we SceneBeans is a client-side library to be downloaded across the net, and so we are trying to keep download times to an absolute minimum. -- NatPryce


Can anyone explain the conceptual difference between serialization of "edges" and serialization of "show nothing" objects in this example?

The difference is that a NullObject representing the "edge" of a graph is implicitly created by the data structure itself as part of it's internal housekeeping (in constructors, or when removing a child node from it's parent), and is therefore an implementation detail that is invisible to the user.

A "show nothing" object is explicitly created by the user when they want to be able to toggle the visibility of a sub-graph by switching between the sub-graph and the "show-nothing" object.

And doesn't NullObject always represent some domain concept, which usually is not clearly exposed because of its "non-object" nature? --NikitaBelenki

It represents the edge of a data structure, but in a way that is invisible to client code. It is therefore an internal implementation detail of the data structure, and so not a domain concept.


CategoryNull


EditText of this page (last edited August 28, 2007) or FindPage with title or text search