Skip to content

Composite Design Pattern

Video Lecture

Section Video Links
Composite Overview Composite Overview Composite Overview 
Composite Use Case Composite Use Case Composite Use Case 
Conditional Expressions Conditional Expressions Conditional Expressions 

Overview

The Composite design pattern is a structural pattern useful for hierarchical management.

The Composite design pattern,

  • Allows you to represent individual entities(leaves) and groups of leaves as the same.
  • Is a structural design pattern that lets you compose objects into a changeable tree structure.
  • Is great if you need the option of swapping hierarchical relationships around.
  • Allows you to add/remove components to the hierarchy.
  • Provides flexibility of structure

Examples of using the Composite Design Pattern can be seen in a file system directory structure where you can swap the hierarchy of files and folders, and also in a drawing program where you can group, ungroup, transform objects and change multiple objects at the same time.

Terminology

  • Component Interface: The interface that all leaves and composites should implement.
  • Leaf: A single object that can exist inside or outside a composite.
  • Composite: A collection of leaves and/or other composites.

Composite UML Diagram

Composite Pattern UML Diagram

Source Code

In this concept code, two leaves are created, LEAF_A and LEAF_B, and two composites are created, COMPOSITE_1 and COMPOSITE_2.

LEAF_A is attached to COMPOSITE_1.

Then I change my mind and attach LEAF_A to COMPOSITE_2.

I then attach COMPOSITE_1 to COMPOSITE_2.

LEAF_B is not attached to composites.

./composite/composite_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# pylint: disable=arguments-differ
"The Composite pattern concept"
from abc import ABCMeta, abstractmethod

class IComponent(metaclass=ABCMeta):
    """
    A component interface describing the common
    fields and methods of leaves and composites
    """

    reference_to_parent = None

    @staticmethod
    @abstractmethod
    def method():
        "A method each Leaf and composite container should implement"

    @staticmethod
    @abstractmethod
    def detach():
        "Called before a leaf is attached to a composite"

class Leaf(IComponent):
    "A Leaf can be added to a composite, but not a leaf"

    def method(self):
        parent_id = (id(self.reference_to_parent)
                     if self.reference_to_parent is not None else None)
        print(
            f"<Leaf>\t\tid:{id(self)}\tParent:\t{parent_id}"
        )

    def detach(self):
        "Detaching this leaf from its parent composite"
        if self.reference_to_parent is not None:
            self.reference_to_parent.delete(self)

class Composite(IComponent):
    "A composite can contain leaves and composites"

    def __init__(self):
        self.components = []

    def method(self):
        parent_id = (id(self.reference_to_parent)
                     if self.reference_to_parent is not None else None)
        print(
            f"<Composite>\tid:{id(self)}\tParent:\t{parent_id}\t"
            f"Components:{len(self.components)}")

        for component in self.components:
            component.method()

    def attach(self, component):
        """
        Detach leaf/composite from any current parent reference and
        then set the parent reference to this composite (self)
        """
        component.detach()
        component.reference_to_parent = self
        self.components.append(component)

    def delete(self, component):
        "Removes leaf/composite from this composite self.components"
        self.components.remove(component)

    def detach(self):
        "Detaching this composite from its parent composite"
        if self.reference_to_parent is not None:
            self.reference_to_parent.delete(self)
            self.reference_to_parent = None

# The Client
LEAF_A = Leaf()
LEAF_B = Leaf()
COMPOSITE_1 = Composite()
COMPOSITE_2 = Composite()

print(f"LEAF_A\t\tid:{id(LEAF_A)}")
print(f"LEAF_B\t\tid:{id(LEAF_B)}")
print(f"COMPOSITE_1\tid:{id(COMPOSITE_1)}")
print(f"COMPOSITE_2\tid:{id(COMPOSITE_2)}")

# Attach LEAF_A to COMPOSITE_1
COMPOSITE_1.attach(LEAF_A)

# Instead, attach LEAF_A to COMPOSITE_2
COMPOSITE_2.attach(LEAF_A)

# Attach COMPOSITE1 to COMPOSITE_2
COMPOSITE_2.attach(COMPOSITE_1)

print()
LEAF_B.method()  # not in any composites
COMPOSITE_2.method()  # COMPOSITE_2 contains both COMPOSITE_1 and LEAF_A

Output

python ./composite/composite_concept.py

LEAF_A          id:2050574298848
LEAF_B          id:2050574298656
COMPOSITE_1     id:2050574298272
COMPOSITE_2     id:2050574298128

<Leaf>          id:2050574298656        Parent: None
<Composite>     id:2050574298128        Parent: None    Components:2
<Leaf>          id:2050574298848        Parent: 2050574298128
<Composite>     id:2050574298272        Parent: 2050574298128   Components:0

SBCODE Editor

<>

Composite Example Use Case

Demonstration of a simple in memory hierarchical file system.

A root object is created that is a composite.

Several files (leaves) are created and added to the root folder.

More folders (composites) are created, and more files are added, and then the hierarchy is reordered.

Composite Example UML Diagram

Composite Pattern Use Case UML Diagram

Source Code

./composite/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"A use case of the composite pattern."

from folder import Folder
from file import File

FILESYSTEM = Folder("root")
FILE_1 = File("abc.txt")
FILE_2 = File("123.txt")
FILESYSTEM.attach(FILE_1)
FILESYSTEM.attach(FILE_2)
FOLDER_A = Folder("folder_a")
FILESYSTEM.attach(FOLDER_A)
FILE_3 = File("xyz.txt")
FOLDER_A.attach(FILE_3)
FOLDER_B = Folder("folder_b")
FILE_4 = File("456.txt")
FOLDER_B.attach(FILE_4)
FILESYSTEM.attach(FOLDER_B)
FILESYSTEM.dir()

# now move FOLDER_A and its contents to FOLDER_B
print()
FOLDER_B.attach(FOLDER_A)
FILESYSTEM.dir()

./composite/file.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
"A File class"
from interface_component import IComponent


class File(IComponent):
    "The File Class. The files are leaves"

    def __init__(self, name):
        self.name = name

    def dir(self, indent):
        parent_id = (id(self.reference_to_parent)
                     if self.reference_to_parent is not None else None)
        print(
            f"{indent}<FILE> {self.name}\t\t"
            f"id:{id(self)}\tParent:\t{parent_id}"
        )

    def detach(self):
        "Detaching this file (leaf) from its parent composite"
        if self.reference_to_parent is not None:
            self.reference_to_parent.delete(self)

./composite/folder.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"A Folder, that acts as a composite."

from interface_component import IComponent


class Folder(IComponent):
    "The Folder class can contain other folders and files"

    def __init__(self, name):
        self.name = name
        self.components = []

    def dir(self, indent=""):
        print(
            f"{indent}<DIR>  {self.name}\t\tid:{id(self)}\t"
            f"Components: {len(self.components)}")
        for component in self.components:
            component.dir(indent + "..")

    def attach(self, component):
        """
        Detach file/folder from any current parent reference
        and then set the parent reference to this folder
        """
        component.detach()
        component.reference_to_parent = self
        self.components.append(component)

    def delete(self, component):
        """
        Removes file/folder from this folder so that self.components"
        is cleaned
        """
        self.components.remove(component)

    def detach(self):
        "Detaching this folder from its parent folder"
        if self.reference_to_parent is not None:
            self.reference_to_parent.delete(self)
            self.reference_to_parent = None

./composite/interface_component.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""
A component interface describing the common
fields and methods of leaves and composites
"""
from abc import ABCMeta, abstractmethod


class IComponent(metaclass=ABCMeta):
    "The Component Interface"

    reference_to_parent = None

    @staticmethod
    @abstractmethod
    def dir(indent):
        "A method each Leaf and composite container should implement"

    @staticmethod
    @abstractmethod
    def detach():
        """
        Called before a leaf is attached to a composite
        so that it can clean any parent references
        """

Output

python ./composite/client.py
<DIR>  root             id:2028913323984        Components: 4
..<FILE> abc.txt        id:2028913323888        Parent: 2028913323984
..<FILE> 123.txt        id:2028913323792        Parent: 2028913323984
..<DIR>  folder_a       id:2028913432848        Components: 1
....<FILE> xyz.txt      id:2028913433088        Parent: 2028913432848
..<DIR>  folder_b       id:2028913433184        Components: 1
....<FILE> 456.txt      id:2028913434432        Parent: 2028913433184

<DIR>  root             id:2028913323984        Components: 3
..<FILE> abc.txt        id:2028913323888        Parent: 2028913323984
..<FILE> 123.txt        id:2028913323792        Parent: 2028913323984
..<DIR>  folder_b       id:2028913433184        Components: 2
....<FILE> 456.txt      id:2028913434432        Parent: 2028913433184
....<DIR>  folder_a     id:2028913432848        Components: 1
......<FILE> xyz.txt    id:2028913433088        Parent: 2028913432848

SBCODE Editor

<>

New Coding Concepts

Conditional Expressions (Ternary Operators).

In ./composite/composite_concept.py, there are two conditional expressions.

Conditional expressions an alternate form of if/else statement.

id(self.reference_to_parent) if self.reference_to_parent is not None else None

If the self.reference_to_parent is not None, it will return the memory address (ID) of self.reference_to_parent, otherwise it returns None.

This conditional expression follows the format

value_if_true if condition else value_if_false

e.g.,

1
2
3
SUN = "bright"
SUN_IS_BRIGHT = True if SUN == "bright" else False
print(SUN_IS_BRIGHT)

or

1
2
3
ICE_IS_COLD = True
ICE_TEMPERATURE = "cold" if ICE_IS_COLD == True else "hot"
print(ICE_TEMPERATURE)

or

1
2
3
4
CURRENT_VALUE = 99
DANGER = 100
ALERTING = True if CURRENT_VALUE >= DANGER else False
print(ALERTING)

Visit https://docs.python.org/3/reference/expressions.html#conditional-expressions for more examples of conditional expressions.

Summary

  • The Composite design pattern allows you to structure components in a manageable hierarchical order.
  • It provides flexibility of structure since you can add/remove and reorder components.
  • File explorer on Windows is a very good example of the composite design pattern in use.
  • Any system where you need to offer at runtime the ability to group, ungroup, modify multiple objects at the same time, would benefit from the composite design pattern structure. Programs that allow you to draw shapes and graphics will often also use this structure as well.