10.1. Composite

  • EN: Composite

  • PL: Kompozyt

  • Type: object

10.1.1. Pattern

  • Represent a hierarchy of objects

  • Groups (and subgroups) objects in Keynote

  • Files in a Folder; when you move folder you also move files

  • allows you to represent individual entities and groups of entities in the same manner.

  • is a structural design pattern that lets you compose objects into a tree.

  • is great if you need the option of swapping hierarchical relationships around.

  • makes it easier for you to add new kinds of components

  • conform to the Single Responsibility Principle in the way that it separates the aggregation of objects from the features of the object.

../../_images/designpatterns-composite-pattern.png

10.1.2. Problem

design-patterns/structural/img/designpatterns-composite-problem.png

from dataclasses import dataclass, field


class Shape:
    def render(self) -> None:
        print('Render Shape')


@dataclass
class Group:
    objects: list[Shape | 'Group'] = field(default_factory=list)

    def add(self, obj: Shape|'Group') -> None:
        self.objects.append(obj)

    def render(self) -> None:
        for obj in self.objects:
            obj.render()


if __name__ == '__main__':
    group1 = Group()
    group1.add(Shape())  # square
    group1.add(Shape())  # square

    group2 = Group()
    group2.add(Shape())  # circle
    group2.add(Shape())  # circle

    group = Group()
    group.add(group1)
    group.add(group2)
    group.render()

10.1.3. Solution

../../_images/designpatterns-composite-solution.png

from abc import ABC, abstractmethod
from dataclasses import dataclass, field


class Component(ABC):
    @abstractmethod
    def render(self) -> None:
        pass

    @abstractmethod
    def move(self) -> None:
        pass


class Shape(Component):
    def move(self) -> None:
        print('Move Shape')

    def render(self) -> None:
        print('Render Shape')


@dataclass
class Group(Component):
    components: list[Component] = field(default_factory=list)

    def add(self, component: Component) -> None:
        self.components.append(component)

    def render(self) -> None:
        for component in self.components:
            component.render()

    def move(self) -> None:
        for component in self.components:
            component.move()


if __name__ == '__main__':
    group1 = Group()
    group1.add(Shape())  # square
    group1.add(Shape())  # square

    group2 = Group()
    group2.add(Shape())  # circle
    group2.add(Shape())  # circle

    group = Group()
    group.add(group1)
    group.add(group2)
    group.render()
    group.move()

10.1.4. Assignments

from abc import ABC, abstractmethod
from dataclasses import dataclass, field


class Shape(ABC):
    @abstractmethod
    def print(self):
        pass


class Ellipse(Shape):
    def print(self):
        print('Ellipse')


class Circle(Shape):
    def print(self):
        print('Circle')


@dataclass
class Group(Shape):
    children: list = field(default_factory=list)

    def add(self, graphic):
        self.children.append(graphic)

    def print(self):
        for children in self.children:
            children.print()


if __name__ == '__main__':
    group1 = Group()
    group1.add(Ellipse())

    group2 = Group()
    group2.add(Circle())
    group2.add(group1)
    group2.print()