Card 06/ 08
Child Methods on the Component Interface
Put add() on Component and a leaf must refuse it; leave it off and the client must know which it holds.
The Composite pattern cannot make leaves and composites fully interchangeable, because only a composite can hold children. Where add is declared decides who pays for that difference, and the Gang of Four name the two choices transparency and safety. This card shows both, the error each one allows, and how to choose.
Transparencyadd on Component
Every node has add, so a client can fill any node without checking its type. A leaf must refuse at run time.
class Component:
def add(self, child):
raise TypeError("cannot hold children")The mistake reaches production as an exception.
Safetyadd on Composite only
Only a composite has add, so a type checker refuses leaf.add(...) before the code runs.
class Composite(Component):
def add(self, child):
self._children.append(child)A client that builds the tree must know, or check, which kind of node it holds.
class Component:
def size(self) -> int:
raise NotImplementedError
def add(self, child: "Component") -> None:
raise TypeError(f"{type(self).__name__} cannot hold children")
class File(Component):
def __init__(self, size_kb: int) -> None:
self._size_kb = size_kb
def size(self) -> int:
return self._size_kb
class Folder(Component):
def __init__(self) -> None:
self._children: list[Component] = []
def add(self, child: Component) -> None:
self._children.append(child)
def size(self) -> int:
return sum(child.size() for child in self._children)
def fill(component: Component) -> None:
component.add(File(4))
print(f"added, size now {component.size()} KB")
fill(Folder())
try:
fill(File(2))
except TypeError as error:
print(f"TypeError: {error}")added, size now 4 KB
TypeError: File cannot hold childrenThe transparent version lets fill take any component, and the folder accepts the file. The file refuses, and the refusal is a TypeError that only a test or a user will find. Swing chose transparency: JComponent extends Container, so a JButton is a container and button.add(panel) compiles, because the method is inherited.
Choose safety when the tree is built in a few known places and read in many, which is the usual case: the builder knows what it is creating, and every reader uses only the shared operations. Choose transparency when generic code must build trees from nodes it cannot see, such as a parser filling a tree from a file, and make the leaf's refusal loud and specific.
← → move through sections and cards