9.1. Composite

9.1.1. Rationale

  • EN: Composite

  • PL: Kompozyt

  • Type: object

9.1.2. Use Cases

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

9.1.3. Problem

from dataclasses import dataclass, field
from typing import Union


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


@dataclass
class Group:
    __objects: list[Union[Shape,'Group']] = field(default_factory=list)

    def add(self, obj: Union[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()

9.1.4. Design

../../_images/designpatterns-composite-gof.png

9.1.5. Implementation

../../_images/designpatterns-composite-usecase.png
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass, field


class Component(metaclass=ABCMeta):
    @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()

9.1.6. Assignments

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


class Shape(metaclass=ABCMeta):
    @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()