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 aleaf
- a composite class, also being
component
, containing a set of multipleleafs
.
The tree client works on a composite that has the ability to manage all objects in the data structure.
Example¶
The following example shows a simple implementation that consists of the following parts:
Line
, interface representingcomponent
DottedLine
andSolidLine
, which are implementations of theLine
interface (these are the leaf objects)CompoundLine
being a composite, that is, a class that groups many objects togetherLine
(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).