From NullObject pattern...
Can some of the contributers help me refactor this page? The new comment on TheNilObject leads me to think some of this material should move to a new TheNilObjectDiscussion page. That, together with renaming this pattern "DummyObject" or NotAnObject? or DullObject -- almost anything other than NullObject. I see, on rereading this, that my own comments mostly resulted (I think) from my confusing NullObjectPattern? with NilObjectPattern? - and I've been convinced that they're different.
And yet, TheNilObject page alludes to the role that NilObject? (or anything like it) might play in a strongly typed language. I wonder if there might be hackery to make it work -- but if so, that belongs on a different page than this.
I wonder if, with the NilObject? stuff removed, the PatternFormerlyKnownAsNullObjectPattern? might become much more interesting again.
-- TomStambaugh
Material moved to TheNilObjectInStronglyTypedLanguages
As I see it, "NULL" in C++ is not a type or subtype, it's a value. (NullValue) C++ lets you convert the NULL value to the value of a pointer to any type you wish, including pointers to objects. But when a C++ pointer contains the value NULL, it does not point to any object. Thus, you can't invoke messages on it. (Doing so is undefined in the language -- and typically causes your program to crash.)
In C++ you must check for NULL before sending a message, or risk program crash. Use of the NullObject pattern relieves you of this inconvenience, by providing a valid pointer to an actual object to use, instead of a "NULL pointer." -- JeffGrigg
Obviously NULL is a value that can be assigned to a pointer, and in that sense NullValue is right, but we can also think of it as a reference to an object and talk about that object's type. Its type conforms to every other type (including non-class types like int and float), and it implements every operation to have undefined behaviour. (In relaxed English we sometimes use NULL to refer to the object, and sometimes to the pointer to the object; I hope this does not cause real confusion.)
This notion is explicit in languages like Eiffel, and it fits in C++ as well as it can given C++'s broken type system. Obviously it may not be implemented that way - eg there may not be a real vtbl that contains an entry for every known function. That said, it might be implemented that way by a system which wanted to provide Java's NullPointerException and didn't have hardware support. Do you see that this (a) would work and (b) would be permitted under the ISO standard? -- DaveHarris
The problems you outline seem to be caused by the use of untyped languages rather than strongly typed languages. The intent of the NullObject pattern as I understand it, is to define different NullObject classes for each base type, rather than use a NullObject class for more than one type. In the Shape example above, the Null_Shape class can only be used where a Shape type is expected. Each type hierarchy that uses the NullObject pattern defines a NullObject class that knows how to act as "null" in the protocol defined by that type. For example, ImmutableList defines a NullObject class that acts as the tail of the list and responds to the "remove" and "forAll" methods appropriately.
In a strongly typed language it would not make sense to ask a NullZeetix? if it is not a Foobar because the type system would check this at compile time. I'm assuming the Foobar and Zeetix are not related by inheritance or share a common base type. If they do, then they would both share a common NullObject and there would be no need for the NullFoobar? and NullZeetix? classes.
-- NatPryce
The problems I describe are present whether or not the system is strongly-typed; the use of strong typing only moves the problem somewhere else (for example, parameterized collections).
Your final paragraph has, in fact, circled back to the root of the problem (no pun intended). In a strongly-typed system in which both Zeetix and Foobar inherit from a common parent (let's call it ZeetixFoobar?), and NullZeetixFoobar? descends from ZeetixFoobar?, what should NullZeetixFoobar?>>isFoobar and NullZeetixFoobar?>>isZeetix respond?
Further, how shall Null<Anything> respond to "== Nil"?
"Nil" (sometimes spelled NULL) is a very funny object; in LISP, it is the only entity that is both an Atom and a List.
The problem is in the semantics, not the language.
-- TomStambaugh
The semantics of a NullObject are determined as much by the clients of the class as by the semantics of the class itself. A NullObject is a subtype of a class. It is responsible for implementing the semantics of the class in a specialized circumstance, in this case the absence of an instance of that class. Different clients might require different behaviors from the non object. So you may define more than one NullObject per class. As an overly simplified example you might have NullObject-s for some numerical types that act like zeros for purposes of addition and subtraction and like ones for purposes of multiplication and division. If you need to test for null you can inherit from a "Nullable" interface that has an isNull method in the base class, NullObjects would then respond "true" while other types would respond "false". This is the approach Doug Lea uses in his book on multithreaded programming in Java. -- PhilGoodwin
You say: "In a strongly-typed system in which both Zeetix and Foobar inherit from a common parent (let's call it ZeetixFoobar?), and NullZeetixFoobar? descends from ZeetixFoobar?, what should NullZeetixFoobar?>>isFoobar and NullZeetixFoobar?>>isZeetix respond?"
I think the question should be why is the program querying the type of an object instead of using polymorphism? If one replaces conditionals on type with polymporphic messages declared as abstract in the base class, the NullZeetixFoobar? object can implement those methods to perform appropriate "no-op" actions.
To continue my shape example...
Instead of code doing something like:
interface Shape { boolean isRectangle(); boolean isCircle(); ... } void drawShape( Shape shape ) { if( shape == null ) return; // Check for null shapes if( shape.isRectangle() ) { drawRectangle(); } else if( shape.isCircle() ) { drawCircle(); } ... }One defines the operations one wants to do to a shape in the Shape interface:
interface Shape { draw( Graphics2D graphics_context ); // Draws the shape onto a device int getArea(); // Returns the area of the shape boolean containsPoint( Point2D p ); // Does the shape contain a point? }Now one draws the shape like this:
void drawShape( Shape shape ) { if( shape == null ) return; // this will be replaced by the NullObject pattern shape.draw( getGraphicsContext() ); }All this is straightforward OO design, but then allows one to use the NullObject pattern to avoid the check for null:
class NullShape implements Shape { public void draw( Graphics2D g ) { /* Don't draw */ } public int getArea() { return 0; } public boolean containsPoint( Point2D p ) { return false; } } void drawShape( Shape s ) { s.draw( getGraphicsContext() ); }Thus, in a similar way that polymorphism removes the need for "isFoobar" tests, the NullObject pattern removes the need for "== null" tests. Indeed, you should never have a null reference within a data structure that uses NullObjects.
The NullObject pattern works well as long as the base type (Shape in this example) defines all the operations one would wish to apply to a Shape. However, if this is not the case, one needs to use the Visitor pattern to double-dispatch on the type of the Shape. In this case, should NullShapes? be included in the Visitor interface? I am suggesting that they should not, and that NullObjects should not double-dispatch to a Visitor. I guess this answers your question as to whether a NullObject should be included in an enumeration of its base type: no it should not. -- NatPryce
Guys, I understand how polymorphism works, you simply polymorphed away my example. The question remains -- when the value is null, should the "type" of the value be Nil, NullFoobar?, or NullFoobarZeetix?? I argue that for it to truly act like "Nil", which it should, it should inherit from UndefinedObject. Even if you don't *need* to send it an "isNil" message, it should still do the right thing in response to it. There are likely to be many occasions in a real system when you want NullZeetix? to act like nil. For example, there are likely to be methods like the following:
Object>>doSomethingWith: anObject "..." self == anObject ifTrue: [^self doSomethingInteresting]. "..."This code is intended to be general, it obeys the Liskov rules, and I argue that "aNullObject doSomethingWith: nil" should answer the result of #doSomethingInteresting.
For this to work, NullFoobar? would need to be a descendent of UndefinedObject. Yet, by construction, NullFoobar? is supposed to be a descendent of AbstractFoobar?. In a single-inheritance system, it cannot be both.
NatPryce's argument, in my view, is tantamount to saying "The NullObject pattern works well as long as you don't do things that don't work". Again, by construction, the only time you need NullObject in the first place is when defining a type that, by construction, includes NULL in its list of acceptable values. A "strong" type system that allows a variable to accept an assignment of a value that is excluded from an enumeration of all possible values is, in my view, broken by definition. Let me try it in different words: given a strong type system that has accepted an assignment of a given value to a variable declared to be of an extensional type "Foobar", I argue that I should surely be able to then apply an assertion like: "Foobar allValues includes: aVar" and get a "true" result.
Let me ask a related question:
Suppose I have a variable declared
zeetix aVariable;What should should the compiler do in response to the following assignments?
aVariable = nilZeetix; aVariable = nilFoobar; aVariable = nil;How shall the following conditionals respond, given these assignments?
if (aVariable == nilZeetix) {/* ... */}; if (aVariable == nilFoobar) {/* ... */}; if (aVariable == nil) {/* ... */};And, perhaps the most interesting:
Object aVariable; Object anotherVariable; #ifdef CASE 1 anotherVariable = nilZeetix; aVariable = nilZeetix; #endif #ifdef CASE 2 anotherVariable = nilZeetix; aVariable = nilFoobar; #endif /* ... */ /* You get the picture */ if (anotherVariable == aVariable) {/* ... */};Enjoy... --TomStambaugh
This page has got way to big for me to figure out what is going on. It sometimes sounds like the problem is multiple inheritance and not nil at all. In a type system like Cecil's,
aVariable = nil;would be a type error because nil is not a zeetix.
And this is a tip-off that something important is happening here! --TomStambaugh
Nor is nilFoobar unless you made it one. If Foobar and Zeetix are disjoint types, you obviously can't define a class nilFoobarZeetix which inherits from both of them. You need separate, more specific classes instead. Nil is not equal to nilFoobar unless you want it to be; we don't have to make all flavours of nil equal to each other.
Excuse me? If you want to be able to correctly perform generic comparisons of objects, and "nil" is one of the objects, you'd better make them all at least appear to be equal to each other. -- TomStambaugh
I'm afraid I don't follow what you mean by "generic comparisons" which implies nil must be equal to nilFoobar. It seems obvious to me they are different. For example, nilFoobar can be used in places a Foobar is expected - it has all the right methods and won't give messageNotUnderstood - but nil cannot. How can they be equal if they are not even substitutable for each other? -- DaveHarris
Being more specific is sometimes necessary and desirable.
I understand there are questions about NullObjects which a given design needs to answer. I don't see that the questions are unanswerable or problematic. -- DaveHarris
Perhaps the difficulty here is that, as is often the case with singularities, the effort to examine the singularity expands in unexpected ways to a variety of areas.
I think DaveHarris is correct in observing that the issue is intimately related to type -- including MultipleInheritance.
As I understand typed systems, especially "strongly typed" systems, they attempt to impose a constraint that any given datum is an instance of one and only one type. Typically, "sub-types" are value restrictions on their supertypes: if "Integer" is a subtype of "Number", Number contains instances that are not included in "Integer", but every Integer is also a Number.
A datum can belong to many types at once; "Integer" and "Number" are both types and a single value can belong to both of them.
We can define sub-typing in terms of Liskov Substitutability, and we can approximate LSP by whether an object will give #messageNotUnderstood. Thus "Integer" is a sub-type of "Number" if "Integer" never gives #messageNotUnderstood when "Number" wouldn't. We can leave inheritance out of it. -- DaveHarris
Thus, what we have here is a situation where NullFoobar? wants to be both a kind of UndefinedObject and a kind of Foobar. In a system with multiple inheritance, this is easier, but not complete -- even with multiple inheritance, sometimes NullFoobar? should be identical to NullZeetix? (in generic comparisons, for example) and sometimes it should not (anytime you want NullZeetix? to behave differently from NullFoobar? -- presumably it is an error for a variable declared to be of type NullFoobar? to contain and instance of NullZeetix? instead).
One approach, in a multiple inheritance system, is to declare NullFoobar? to be a Singleton that has both UndefinedObject and Foobar as parents. In a single-inheritance system, this can be simulated by choosing one parent and delegating to the other. However, even this approach will lead to unexpected behavior in generic comparisons (consider, for example, UI widgets that return a list selection. What object should a Foobar chooser answer when there is no selection, nil or theNilFoobar?).
I offer that the issue is created by explicitly representing nil in the first place (in a strongly typed system). I'm reminded of the difference between a scavenging garbage collecter and a mark/sweeper: the former never explicitly dereferences the garbage, it instead leaves it behind. This is a fundamental difference that leads to profound differences in garbage collector behavior and performance.
If someone wants to refactor this into another set of pages, its fine with me -- but I think that this is all very relevant to the use of the NullObject pattern. The rub, for me, is that each time I've used it, it has come back to haunt me, through these kinds of issues. I'm therefore loath to simply dismiss these questions as irrelevant.
-- TomStambaugh
Sounds to me like a purely semantic argument taking issue with a (possibly) misleadingly named pattern -- this could probably be resolved by replacing the word Null in the NullObject pattern name. People seem to be more hung up on the idea that the term Null shows up in its name than with the actual behavior of the pattern. Seeing the term 'null' moves one to desire implementations of NullObject that act like null. However, this misses the point. The null value that NullObject replaces is a misuse of the null/nil value. Since there is no concept for absent behavior, we have little choice but to use the absence of value to denote the absence of behavior in our code. In this context, NullObject does not represent a null value but is instead representing empty or non-behavior. Seems this argument could be cleared up by renaming the pattern something along the lines of DoNothingObject?, NoBehaviorObject?, or TransparentObject?. From my limitted understanding, the pattern's intent seems simple -- to provide an empty implementation in order to reduce value-checking logic. In many cases, the use of this pattern can be replaced by ObjectForStates? while in other cases it works great with ObjectForStates?.
Back to an earlier quote implying that null acts like a subclass in C++ or Eiffel... it is just because null does not act like a subclass that something like NullObject is so useful.
I've been playing with a pattern to replace NullObject that is a variation on AbstractClass. I'm thinking of calling it something like DefaultAbstractClass?, DefaultBehaviorClass?, or even ReplaceAbstractClassWithDefaultBehaviorClass?. Basically, take the AbstractClass pattern and anywhere one would implement a method in an AbstractClass with "SubclassResponsibility?" should instead be implemented with NullObject-like behavior. Consider the following typical example of using the NullObject pattern with an IteratorClass?.
class NullIterator extends Iterator { public void reset() { } public bool isDone() { return true; } public Object at() { return null; } public Object next() { return at(); } }However, what if we reversed this and instead of being a NullIterator?, it was an 'un-specialized' iterator or DefaultIterator?
class DefaultIterator { public void reset() { } public bool isDone() { return true; } public Object at() { return null; } public Object next() { return at(); } }Concrete iterators would subclass DefaultIterator much like an AbstractClass. While not shown in this example, a concrete class would only need specialize those methods for which they were adding non-default behavior.
In this way, subclasses would gradually specialize the default (or null) behavior out of existence. So rather than NullObject being a specialization of some abstract class, the null object would be the classes default implementation. Any thoughts on this? Clearly there are some problems with this idea too, but is it worth working into its own pattern?
You said... "I argue that for it to truly act like "Nil", which it should, it should inherit from UndefinedObject."
The point of a NullObject is not to act like a null object reference, but to do something sensible at the edges of a data structure so you never have a null reference in the data structure.
You complain that I "simply polymorphed away [your] example.
That's the whole point! The NullObject pattern replaces conditionals testing for null object references in data structures with polymorphism. It's still perfectly valid to have null object references elsewhere (in local variables for example), but they are a programming error if encountered within a data structure. For example, you may have a function performing a depth-first search over a tree that can contain NullObjects. The search function would return a null object reference to indicate that it had not found the required node. It would not return a NullObject to indicate that because a NullObject is used to mark the edges of a graph, not the absence of an object.
To touch on the issue of types and semantics that you have mentioned, a NullObject is not a null reference. A null reference is a value of the reference type, and a NullObject is a value of the polymorphic type referred to by a reference.
-- NatPryce
You said "you may have a function performing a depth-first search over a tree that can contain NullObjects. The search function would return a null object reference to indicate that it had not found the required node. It would not return a NullObject to indicate that because a NullObject is used to mark the edges of a graph, not the absence of an object."
And so when I assign the result of this search into a variable, I'll be assigning the value "nil" to a variable declared to be of type Foobar.
Now please tell me how I subsequently use that variable without testing its value for nil at each reference -- precisely the problem I set out to avoid with the NullObject pattern. -- TomStambaugh
The fact that you are testing the returned reference against null is because that is not an example of the NullObject pattern, but the pattern is used within the structure itself. Look at the applicability of the pattern as described at the top of the [NullObject] page: "a recursive structure, like a list or a tree, requires some special marker to define those nodes at the structure's boundary".
The NullObject pattern is not used to replace all uses of null references, or all tests for null references. Only null references marking the boundaries of a data structure made up of polymorphic objects, and checks for null in code that traverses that data structure. Like any pattern, it is applicable in some parts of a system and makes no sense in (and, indeed, complicates) other parts.
-- NatPryce
Yes, so it sounds like we agree -- although we express our interpretation differently. You said "... it is applicable in some parts of a system and makes no sense in (and, indeed, complicates) other parts.". I said "Unfortunately, I fear that this pattern moves, rather than solves, the problems it attempts to address.". I believe these are two essentially equivalent statements, albeit with different connotations (I don't share your sense that this is true for any pattern).
I "had" this pattern at one time, and have since stopped using it because my experience has been that the "other parts" that it "complicates" or "makes no sense in" in were a) unpredictable (by me) and b) just as painful to solve as the original tests I was trying to avoid through the use of NullObject.
While this exchange has helped me more clearly understand the ramifications of NullObject (it has helped me understand more completely the extent of the "weirdness" of nil in a strongly-typed environment), perhaps we agree that application of NullObject does have side effects that tend to offset (to a greater or lesser extent) its utility. -- TomStambaugh
A generic collection should not return the object that isn't there. If you want the collection returning some particular object in some particular cases, just write an adapter.
When you are assigning an object of type FoobarOrNil? to a Foobar variable, that's your fault. As well as when you are assigning the FoobarZeetix? object to the same variable. A system with relatively good type checking will find these faults (even CeePlusPlus can sometimes find them). A good strong typed system will force you to perform the type conversion safely. -- NikitaBelenki
I guess the reason we have had this exchange is that our experiences of the pattern are quite different. In the systems in which I found the pattern useful (the animation scene-graph, for example), the parts of the system in which it was and was not applicable were very easy to determine, and the pattern cleaned up the code enormously. On the other hand, my experience is also that I never designed the NullObject pattern into the system from the start, but added it later when checks for null started becoming unwieldy. Perhaps it is better thought of as a refactoring pattern? -- NatPryce.
I think of all the design patterns as refactoring patterns.
I think of nil and NullFoobar? quite differently. nil means uninitialized. NullFoobar? means missing on purpose. There is a big difference between never initialized and initialized to be absent. -- RalphJohnson
Sometimes you need several different "flavors" of NULL. The "OWL" object oriented language in EDS (...similar to Smalltalk; used for expert systems development), had several different kinds of NULL:
I can't help wondering if this debate would be clarified by simply renaming the pattern from NullObject to DummyObject -- DaveWhipp
Absolutely, and I should have read your comment before responding, as I ended up taking many more words to say the same thing! -- RobertDiFalco
Most of what I'd wanted to say has been said; for the sake of summarizing:
I also think ObjectOriented programming is mostly just a matter of organizing your code: you put together things that will tend to change together. You don't actually write very much code you wouldn't have written in a procedural incarnation: the main benefits are in being able to reduce duplication (though you get out of writing some bookkeeping code when the language supports OO), and to make most changes much, much more cheaply.
To elaborate on DummyObject as something that's called into existence to eliminate null checks: Tom asked whether a DummyFoobarZeetix? should act like a Foobar or like a Zeetix. Assuming our motivation to introduce DummyFoobarZeetix? is the usual one, we want it to behave the way null would have behaved.
Since we had no way of distinguishing between various values of null before, Foobar and Zeetix should behave exactly the same as each other when null. We move that behavior into DummyFoobarZeetix?. If Foobar and Zeetix had had different null behavior, we might not have been tempted to create DummyFoobarZeetix? in the first place - or even FoobarZeetix?.
And since all occurrences of null are the same (there being no instances to differ), it should be clear that we only ever need one instance of DummyFoo?, and that it's a value object: it can't change its state (the question "can it examine its state?" is meaningless for a value object).
As for what a DummyObject should say when you ask it about its type: I honestly think asking an object about its type is cheating. It's roughly like conducting a Turing test and saying, "Yeah, but are you a computer?" This came up with regard to reflection on the LiskovSubstitutionPrinciple page, too. There should be some way to shave this kind of pointless hair (the "type name") off of types: if the types' behavior differs in no other way than what name they give back when asked, why would you have them return different names in the first place?
I guess this also ties into BlackHolesHaveNoHair. A shameless RecentChangesJunkie am I. -- GeorgePaci
I think the term "dummy" is just too vague. Dummy can mean something that is not yet implemented, something that implements default behaviour for test purposes, a NullObject, etc. etc. Perhaps "Edge Object" or "End Object" would be a better term, considering the intent of this pattern. -- NatPryce.
I find it fascinating how this seemingly simple pattern can result in so much discussion and so many ramifications. I guess this shows how important is the selection of an appropriate name for a pattern (or any other programming construct), and therefore how important it is to refactor the pattern literature as a whole.
Given how often reuse of "Do Nothing Behavior" is mentioned in the PLOP paper, and the heavy confusion over the nil/NULL concept, I kinda' like DoNothingObject?. ;-> -- JeffGrigg
I rather like the name "Inert Object". -- NatPryce
I think I'll start calling it "Dull" -- or "DullObject", in WikiSpeak -- TomStambaugh
I quite like TrivialObject?. Maybe it's the mathematician in me. A trivial collection is an empty one, a trivial rectangle is empty too. ("Degenerate" would be good too.) Going with RobertDiFalco's comment about default interators, it kinda makes sense to make a normal object by inheriting from a trivial one and adding the non-trivial behaviour. -- DaveHarris
The suggestion that all non-trivial rectangles are trivial rectangles (implied by normal inheritance semantics) seems contradictory to me. -- DanielBrockman What do you think the normal inheritance semantics are? Derived BehavesLike? a Base, doesn't it? Yes, and if you leave it at that, then your inheritance makes sense. However, in my experience most people would say that a Derived IS-A Base. Now, a NonTrivialRectangle IS-NOT-A TrivialRectangle: if it were, why would it be called called a NonTrivialRectangle? On the other hand, a NonTrivialRecangle IS-A Rectangle. Compare BlueTriangle <: ColorlessTriangle; while it may be true that a BlueTriangle BehavesLike? a ColorlessTriangle, I'd still say the inheritance is unjustified, because BlueTriangles, by definition, are not ColorlessTriangles. -- db
I think this trivial object idea seems a bit more intuitive than the standard inheritance. If you think of the trivial object - as not necessarily a direct analogue of the null object - but as a more generalized version of the object, it gives you a base class that cannot contain data(that is null), and you define a subclass that has the the ability to contain data other than null.
Sometimes you will not want a NullObject to do nothing at all - just not to do something in particular. Or is this another pattern? -- NikitaBelenki
You are absolutely right. Very often, NullObjects do something, rather than nothing. They tend to implement "default" behaviour, which is the reason why I tend to refer to this as the DefaultObject pattern. -- AnthonyLauder
See also: BottomPropagation and/or ExceptionalValue.