Some time ago, in my incarnation as an academic researcher, I wrote a Java framework for animating 2D graphics called Scene Beans. While adding features to the framework I reused a Null Object class in a way that exposed the class to the user of the framework. This caused unexpected problems during later development. This taught me that Null Objects should not be used to model domain concepts but should be treated as internal implementation details of a framework and hidden from client code.
The SceneBeans framework provides a scene graph data structure that defines a graphical scene as a directed graph of polymorphic scene-graph "nodes" that are processed by Visitors. I used the Null Object pattern to mark the edges of the Scene Graph, and combined the Null Object and Visitor patterns so that the Null Object nodes did not double dispatch to the visitor — as far as the visitors knew, the Null Object 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. I later needed to selectively show or hide parts of the scene. I realised that I could do this by putting a subgraph and a Null Object node into to a switch node; switching between the subgraph and the Null Object would have the effect of showing or hiding the subgraph. This only required changing the Scene Beans file format so that files could explicitly specify Null Objects in the graph. Cunning!
Or so I thought...
I noticed a problem when we wrote a visitor to write a scene graph into a file in our format. The visitor never wrote the Null Objects because they did not double dispatch to it. This was fine for Null Objects at the edge of the graph, but was the wrong behaviour for Null Objects that had been explicitly created to be included in "switch" nodes.
The problem was that my modifications had changed the way that the framework used Null Objects, from using them as an internal implementation detail to mark the edges of the scene graph to using them to represent the concept of "draw nothing" in a user-visible way.
Solutions in reverse order of elegance:
visitNullmethod on the visitor interface, and provide a do-nothing default implementation of
visitNullin the abstract base class from which all visitors are derived.
This experience taught me that when changing code that uses the Null Object pattern, one must beware of modifying the system from using Null Objects as mere implementation details to using Null Object classes to represent domain concepts. This goes against the intent of the Null Object pattern and will cause headaches at later date.