An Python example of tree shows the initial idea of Composite Pattern and Lazy Initialization.
class Node:
def __init__(self, val):
self._val = val
self._lchild = None
self._rchild = None
def set_lchild(self, node):
self._lchild = node
def set_rchild(self, node):
self._rchild = node
def sum(self):
sum = self._val
if self._lchild is not None:
sum += self._lchild.sum()
if self._rchild is not None:
sum += self._rchild.sum()
return sum
root = Node(1)
lchild = Node(2)
root.set_lchild(lchild)
root.sum()
To avoid null check, Null Object Pattern is introduced.
class Node(ABC):
def __init__(self, val):
self._val = val
self._lchild = NullNode(0)
self._rchild = NullNode(0)
def set_lchild(self, node):
self._lchild = node
def set_rchild(self, node):
self._rchild = node
@abstractmethod
def is_null(self):
return NotImplemented
@abstractmethod
def sum(self):
return NotImplemented
class RegularNode(Node):
def is_null(self):
return False
def sum(self):
sum = self._val
if not self._lchild.is_null():
sum += self._lchild.sum()
if not self._rchild.is_null():
sum += self._rchild.sum()
return sum
class NullNode(Node):
def is_null(self):
return True
def sum(self):
return 0
root = RegularNode(1)
lchild = RegularNode(2)
root.set_lchild(lchild)
root.sum()
To obey Liskov Substitution Principle, the example is revised.
class Node(ABC):
@abstractmethod
def is_null(self):
return NotImplemented
@abstractmethod
def sum(self):
return NotImplemented
class RegularNode(Node):
def __init__(self, val):
self._val = val
self._lchild = NullNode()
self._rchild = NullNode()
def set_lchild(self, node):
self._lchild = node
def set_rchild(self, node):
self._rchild = node
def is_null(self):
return False
def sum(self):
sum = self._val
if not self._lchild.is_null():
sum += self._lchild.sum()
if not self._rchild.is_null():
sum += self._rchild.sum()
return sum
class NullNode(Node):
def is_null(self):
return True
def sum(self):
return 0
root = RegularNode(1)
lchild = RegularNode(2)
root.set_lchild(lchild)
root.sum()
This makes RegularNode depend on its sibling NullNode.
And I'm not sure whether the dependency violates Dependency Inversion Principle.
If so, how to satisfy requirements above while no more SOLID Principle is violated?
In the examples above, any attemption will violate an SOLID principle.
The best scenario is no SOLID Principle is violated. Otherwise, which principle would be OK to compromise in normal cases?
The whole point of the Null Object Pattern is to avoid checking whether something is null or not. Instead, the null object does nothing, or returns values appropriate for the "nothing" case.
In your case, you've already done this with the
summethod by declaring theNullNodeimplementation to beSo the
summethod ofRegularNodeno longer needs to check anything about whether the left or right child is null. Instead, the Null Object Pattern lets us simplify thesumto:This works because adding 0 has no effect.
The
is_nullmethod goes away. All that is left in the baseNodeclass is the abstract methodsum. Nice and clean.It's fine for
RegularNodeto depend onNullNodebecause the dependency is one way.