7.1. Composite

  • EN: Composite

  • PL: Kompozyt

  • Type: object

7.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

7.1.2. Problem

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

from typing import Self
from dataclasses import dataclass, field


@dataclass
class Shape:
    name: str

    def render(self) -> None:
        print(f'Render {self.name}')

    def move(self) -> None:
        print(f'Move {self.name}')


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

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

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

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


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

    group2 = Group()
    group2.add(Shape('circle'))
    group2.add(Shape('ellipse'))

    everything = Group()
    everything.add(group1)
    everything.add(group2)

    everything.render()
    # Render square
    # Render rectangle
    # Render circle
    # Render ellipse

    everything.move()
    # Move square
    # Move rectangle
    # Move circle
    # Move ellipse

7.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


@dataclass
class Shape(Component):
    name: str

    def move(self) -> None:
        print(f'Move {self.name}')

    def render(self) -> None:
        print(f'Render {self.name}')


@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('rectangle'))

    group2 = Group()
    group2.add(Shape('circle'))
    group2.add(Shape('ellipse'))

    everything = Group()
    everything.add(group1)
    everything.add(group2)

    everything.render()
    # Render square
    # Render rectangle
    # Render circle
    # Render ellipse

    everything.move()
    # Move square
    # Move rectangle
    # Move circle
    # Move ellipse

7.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()