Skip to content

Composite

Composite is a design pattern that can be used if we are able to represent objects as trees. Such a tree consists of nodes which, from the user's perspective, are a single object.

In order to use the composite, we need the following elements:

  • a common interface or base class for objects in a tree is often called component.
  • a class representing a single object on a branch (which is component). Such an object does not contain any children, often called a leaf
  • a composite class, also being component, containing a set of multiple leafs.

The tree client works on a composite that has the ability to manage all objects in the data structure.

composite

Example

The following example shows a simple implementation that consists of the following parts:

  • Line, interface representing component
  • DottedLine and SolidLine, which are implementations of the Line interface (these are the leaf objects)
  • CompoundLine being a composite, that is, a class that groups many objects together Line (node)
  • usage case
class Point:
    def __init__(self, x, y):
        self._x = x
        self._y = y

    def __str__(self):
        return f"({self._x}, {self._y})"
class Line:
    def draw(self, length):
        pass

    def starting_position(self):
        pass

    def starting_position(self, value):
        pass
class SolidLine(Line):
    def __init__(self):
        self._point = Point(0, 0)

    def draw(self, length):
        print(f"Drawing solid line starting in {self._point} with lenght {length}")

    @property
    def starting_position(self):
        return self._point

    @starting_position.setter
    def starting_position(self, value):
        self._point = value
class DottedLine(Line):
    def __init__(self):
        self._point = Point(0, 0)

    def draw(self, length):
        print(f"Drawing d.o.t.t.e.d line starting in {self._point} with lenght {length}")

    @property
    def starting_position(self):
        return self._point

    @starting_position.setter
    def starting_position(self, value):
        self._point = value
class CompoundLine(Line):
    def __init__(self):
        self._lines = []

    def draw(self, length):
        for line in self._lines:
            line.draw(length)

    @property
    def starting_position(self):
        if not self._lines:
            return Point(0, 0)
        else:
            return self._lines[0].starting_position()

    @starting_position.setter
    def starting_position(self, value):
        for line in self._lines:
            line.starting_position = value

    def add_line(self, line):
        self._lines.append(line)

    def remove_line(self, line):
        self._lines.remove(line)
def main():
    dotted_1 = DottedLine()
    dotted_1.starting_position = (1, 1)

    dotted_2 = DottedLine()
    dotted_2.starting_position = (2, 2)

    solid_1 = SolidLine()
    solid_1.starting_position = (3, 3)

    solid_2 = SolidLine()
    solid_2.starting_position = (4, 4)

    compound_1 = CompoundLine()
    compound_2 = CompoundLine()

    # folding a tree structure
    compound_1.add_line(dotted_1)
    compound_1.add_line(solid_1)
    compound_1.add_line(compound_2)
    compound_2.add_line(dotted_2)
    compound_2.add_line(solid_2)

    # common interface for node and leaf
    compound_2.starting_position = (5, 5)

    solid_2.starting_position = (6, 6)

    compound_1.draw(5)


if __name__ == '__main__':
    main()

Composite is a pattern that is worth using if you want the end user to be able to use one, common interface that can represent a single object, as well as its group, which we are able to present as a tree structure.

The main challenge when trying to use this pattern is finding a common interface for the component object, however when it is done, it is very possible that it will significantly facilitate our navigation through complicated data structures (or we can combine the implementation with the pattern iterator).