How to design Composite Pattern with Lazy Initialization and Null Object Pattern while SOLID Principles are followed?

49 views Asked by At

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?

1

There are 1 answers

0
Jon Reid On BEST ANSWER

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 sum method by declaring the NullNode implementation to be

    def sum(self):
        return 0

So the sum method of RegularNode no longer needs to check anything about whether the left or right child is null. Instead, the Null Object Pattern lets us simplify the sum to:

    def sum(self):
        sum = self._val
        sum += self._lchild.sum()
        sum += self._rchild.sum()
        return sum 

This works because adding 0 has no effect.

The is_null method goes away. All that is left in the base Node class is the abstract method sum. Nice and clean.

It's fine for RegularNode to depend on NullNode because the dependency is one way.