"Therefore: Mark the space just beyond the structure's edge with instances of an appropriate null object. Expect this object to participate in calculations by returning zero, null or empty, as appropriate for any particular algorithm." [1]
class Tree
attr_accessor :left, :right, :value
def initialize( value )
@left = NullTree.new
@right = NullTree.new
@value = value
end
def size
1 + left.size + right.size
end
def sum_of_values
value + left.sum_of_values + right.sum_of_values
end
def product_of_values
value * left.product_of_values * right.product_of_values
end
end
class NullTree
def size
0
end
def sum_of_values
0
end
def product_of_values
1
end
end
tree = Tree.new( 2 )
tree.left = Tree.new( 3 )
tree.left.right = Tree.new( 4 )
tree.left.left = Tree.new( 5 )
p tree.size
p tree.sum_of_values
p tree.product_of_values
Results in the output:
4 14 120
-- NatPryce
As a wee optimization, you could use the SingletonPattern to implement the null class.
require 'singleton'
class NullTree
include Singleton
# ...
end
class Tree
def initialize( value )
@left = NullTree.instance
@right = NullTree.instance
@value = value
end
# ...
end
or even...
class Tree
@@empty_leaf = NullTree.new
def initialize
@left = @right = @@empty_leaf
...
In Ruby, the nil value is itself an example of the NullObjectPattern. You can send the message nil? to any object. The nil value returns true and other objects return false.
I would make the NullTree?'s class a subclass of Tree and provide a predicate to check. Then all of the code for a method can be implemented in Tree and people wanting to extend it only need to extend that.
class Tree; end
NullTree = Class.new(Tree) {
def dup() self; end
def clone() self; end
def null_tree?() true; end
def inspect() 'NullTree'; end
}.new
class Tree
def initialize(val)
@val = val
@left = @right = NullTree
end
def null_tree?() false; end
attr_accessor :val, :left, :right
def size
if null_tree?
0
else
1 + @left.size + @right.size
end
end
end
-- DevinPapineau?
That misses the whole point of the NullObjectPattern. The size method should not care whether a branch is the null_tree or not. The null_tree should return 0 from it's size method. The implementation if Tree::size would then be much simpler:
def size
1 + @left.size + @right.size
end
--NatPryce.