Design Pattern: Composite Pattern in Python

In composite pattern we use the same interface for the same object and composition of objects. This way we can treat and use the individual item object and item list/composition of object exactly the same way.

NOTES

In this article, we discuss the implementation of the Composite Pattern in Python.

See the composite in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Composite Pattern-

Implementation

Here is how to implement the composite pattern in python-

Create an abstract class(or Protocol) that will be used by the individual item class and compsition class.
Create class for individual item and implement the interface(abstract class or protocol).
Create class for compsition/list/group of items and implement the interface(abstract class or protocol).

Here is a simple implementation of Composite pattern in Python-

from abc import ABC, abstractmethod


# Item interface
class Item(ABC):
    @abstractmethod
    def operation1(self) -> None:
        pass

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


# First item class
class Item1(Item):
    def operation1(self) -> None:
        print("Item 1: operation 1")

    def operation2(self) -> None:
        print("Item 1: operation 2")


# Second item class
class Item2(Item):
    def operation1(self) -> None:
        print("Item 2: operation 1")

    def operation2(self) -> None:
        print("Item 2: operation 2")


# Composite class
class ItemGroup(Item):
    def __init__(self) -> None:
        self.item_list = []

    def operation1(self) -> None:
        for item in self.item_list:
            item.operation1()

    def operation2(self) -> None:
        for item in self.item_list:
            item.operation2()

    def add_item(self, item: Item) -> None:
        self.item_list.append(item)


# Demo usage
def main():
    item1 = Item1()
    item2 = Item2()
    another_item1 = Item1()

    # Item operation
    print("Operation from individual object-")
    item1.operation1()
    item1.operation2()

    # Item group operation
    print("Operation from ItemGroup object-")

    item_group = ItemGroup()
    item_group.add_item(item1)
    item_group.add_item(item2)
    item_group.add_item(another_item1)

    item_group.operation1()
    item_group.operation2()


if __name__ == "__main__":
    main()
Python

Output:

Output generated by the code above is –

Operation from individual object-
Item 1: operation 1
Item 1: operation 2

Operation from ItemGroup object-
Item 1: operation 1
Item 2: operation 1
Item 1: operation 1
Item 1: operation 2
Item 2: operation 2
Item 1: operation 2
Plaintext

Examples

We are discussing few examples of Composite pattern. The examples are similar, where we are treating a single item and composite item object using the same interface.

Example #1: Transport List

Transport Interface [Item Interface]

Create an abstract class “Transport” by inheriting “ABC“.
Declare abstract methods- “start“, “stop“, “operate“.
from abc import ABC, abstractmethod
from typing import List


# Transport Interface
class Transport(ABC):
    @abstractmethod
    def start(self) -> None:
        pass

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

    @abstractmethod
    def stop(self) -> None:
        pass
Python

Bike Class [Item Class]

Create “Bike” class.
Inherit from the “Transport” abstract class.
Add the defintion of the methods – “start“, “operate“, “stop“.
# Bike Class
class Bike(Transport):
    def start(self) -> None:
        print("Starting Bike...")

    def operate(self) -> None:
        print("Riding Bike")

    def stop(self) -> None:
        print("Stopping Bike...")
Python

Plane Class [Item Class]

Create “Plane” class.
Inherit from the “Transport” and add the defintion of the methods – “start“, “operate“, “stop“.
# Plane Class
class Plane(Transport):
    def start(self) -> None:
        print("Starting Plane...")

    def operate(self) -> None:
        print("Flying Plane")

    def stop(self) -> None:
        print("Stopping Plane...")
Python

Car Class [Item Class]

Create “Car” class.
Inherit from the “Transport“.
# Car Class
class Car(Transport):
    def start(self) -> None:
        print("Starting Car...")

    def operate(self) -> None:
        print("Driving Car")

    def stop(self) -> None:
        print("Stopping Car...")
Python

Transport Group Class [Composite Class]

Create class “TransportGroup“.
Define an attribute “_transport_list” to store the list of transports.
Include method to add and remove items in the transport list.
Inherit from “Transport“.
In the method defintion make sure to handle the same method for all the items in the list.
# TransportGroup Class
class TransportGroup(Transport):
    def __init__(self):
        self._transport_list: List[Transport] = []

    def start(self) -> None:
        for transport in self._transport_list:
            transport.start()

    def operate(self) -> None:
        for transport in self._transport_list:
            transport.operate()

    def stop(self) -> None:
        for transport in self._transport_list:
            transport.stop()

    def add_transport(self, transport: Transport) -> None:
        self._transport_list.append(transport)

    def remove_transport(self, transport: Transport) -> None:
        if transport in self._transport_list:
            self._transport_list.remove(transport)
Python

Demo

# Demo usage
def main():
    # Instantiate transport objects
    bike = Bike()
    plane = Plane()
    car = Car()
    second_car = Car()

    # Create a transport group
    transports = TransportGroup()
    transports.add_transport(bike)
    transports.add_transport(plane)
    transports.add_transport(car)
    transports.add_transport(second_car)

    print("-----------------Output with 4 transports------------------")
    transports.start()
    transports.operate()
    transports.stop()

    print("\n-----------------Output when plane is removed------------------")
    transports.remove_transport(plane)
    transports.start()
    transports.operate()
    transports.stop()


if __name__ == "__main__":
    main()
Python

Output

-----------------Output with 4 transports------------------
Starting Bike...
Starting Plane...
Starting Car...
Starting Car...
Riding Bike
Flying Plane
Driving Car
Driving Car
Stopping Bike...
Stopping Plane...
Stopping Car...
Stopping Car...

-----------------Output when plane is removed------------------
Starting Bike...
Starting Car...
Starting Car...
Riding Bike
Driving Car
Driving Car
Stopping Bike...
Stopping Car...
Stopping Car...
Plaintext

Example #2: Player Group

Let’s consider a group of players. Using the composite pattern we can call the player description without knowing if we are calling it for an individual player or group of players.

Player Interface [Item Interface]

Create “Player” class.
Inherit from “ABC” to make it an abstract class.
Declare abstract method “print_details“.
from abc import ABC, abstractmethod
from typing import List


# Player Interface
class Player(ABC):
    @abstractmethod
    def print_details(self) -> None:
        pass
Python

Basketball Player Class [Item Class]

Create class “BasketballPlayer“.
In the “__init__” method accept and set name, age, points attributes.
Inherit from “Player” abstract class.
Define method “print_details” and print full details of the player.
# BasketballPlayer Class
class BasketballPlayer(Player):
    def __init__(self, name: str, age: int, points: int):
        self.name = name
        self.age = age
        self.points = points

    def print_details(self) -> None:
        print("Game: Basketball")
        print(f"Name: {self.name}")
        print(f"Age: {self.age}")
        print(f"Points: {self.points}")
Python

Football Player Class [Item Class]

Create class “FootballPlayer“.
In the “__init__” method accept and set name, age, goals attributes.
Inherit from “Player” abstract class.
Define method “print_details” and print full details of the football player.
# FootballPlayer Class
class FootballPlayer(Player):
    def __init__(self, name: str, age: int, goals: int):
        self.name = name
        self.age = age
        self.goals = goals

    def print_details(self) -> None:
        print("Game: Football")
        print(f"Name: {self.name}")
        print(f"Age: {self.age}")
        print(f"Goals: {self.goals}")
Python

Cricket Player Class [Item Class]

Create class “CricketPlayer“.
In the “__init__” method accept and set name, age, runsattributes.
Inherit from “Player” abstract class.
Define method “print_details” and print full details of the cricket player.
# CricketPlayer Class
class CricketPlayer(Player):
    def __init__(self, name: str, age: int, runs: int):
        self.name = name
        self.age = age
        self.runs = runs

    def print_details(self) -> None:
        print("Game: Cricket")
        print(f"Name: {self.name}")
        print(f"Age: {self.age}")
        print(f"Runs: {self.runs}")
Python

Player Group

Create class “PlayerGroup“.
In the “__init__” method initialize atterbute “player_list” to an empty list.
Implement methods for adding and removing items from the “player_list“.
Inherit from “Player” abstract class.
In the “print_details” method implementation call the “print_details” method of all the “Player” items in the list.
# PlayerGroup Class
class PlayerGroup(Player):
    def __init__(self):
        self.player_list: List[Player] = []

    def print_details(self) -> None:
        for player in self.player_list:
            player.print_details()

    def add_element(self, player: Player) -> None:
        self.player_list.append(player)

    def remove_element(self, player: Player) -> None:
        if player in self.player_list:
            self.player_list.remove(player)
Python

Demo

# Demo usage
def main():
    # Under 15 players
    under15_players = PlayerGroup()
    under15_players.add_element(FootballPlayer("FPlayer 15_1", 13, 23))
    under15_players.add_element(FootballPlayer("FPlayer 15_2", 14, 30))
    under15_players.add_element(BasketballPlayer("BPlayer 15_1", 12, 80))
    under15_players.add_element(BasketballPlayer("BPlayer 15_2", 14, 100))
    under15_players.add_element(CricketPlayer("CPlayer 15_1", 14, 467))

    # Under 19 players
    under19_players = PlayerGroup()
    under19_players.add_element(FootballPlayer("FPlayer 19_1", 18, 43))
    under19_players.add_element(BasketballPlayer("BPlayer 19_1", 17, 77))
    under19_players.add_element(CricketPlayer("CPlayer 19_1", 18, 654))
    under19_players.add_element(CricketPlayer("CPlayer 19_2", 16, 789))

    # National team players
    national_team_players = PlayerGroup()
    national_team_players.add_element(FootballPlayer("FPlayer N_1", 18, 43))
    national_team_players.add_element(BasketballPlayer("BPlayer N_1", 17, 77))
    national_team_players.add_element(CricketPlayer("CPlayer N_1", 18, 654))

    # Create a group with all teams
    all_teams = PlayerGroup()
    all_teams.add_element(under15_players)
    all_teams.add_element(under19_players)
    all_teams.add_element(national_team_players)

    # Print details of all players
    all_teams.print_details()


if __name__ == "__main__":
    main()
Python

Output

Game: Football
Name: FPlayer 15_1
Age: 13
Goals: 23

Game: Football
Name: FPlayer 15_2
Age: 14
Goals: 30

Game: Basketball
Name: BPlayer 15_1
Age: 12
Points: 80

Game: Basketball
Name: BPlayer 15_2
Age: 14
Points: 100

Game: Cricket
Name: CPlayer 15_1
Age: 14
Runs: 467

Game: Football
Name: FPlayer 19_1
Age: 18
Goals: 43

Game: Basketball
Name: BPlayer 19_1
Age: 17
Points: 77

Game: Cricket
Name: CPlayer 19_1
Age: 18
Runs: 654

Game: Cricket
Name: CPlayer 19_2
Age: 16
Runs: 789

Game: Football
Name: FPlayer N_1
Age: 18
Goals: 43

Game: Basketball
Name: BPlayer N_1
Age: 17
Points: 77

Game: Cricket
Name: CPlayer N_1
Age: 18
Runs: 654
Plaintext

Example #3: Menu

Let’s print the menu items. We are handling the parent and child items of a menu. We are considering a nested menu.

Menu Interface

Create class “Menu“.
Inherit form “ABC“.
Declare abstract method “print” which will be used to print the menu items.
from abc import ABC, abstractmethod
from typing import List


# Menu Interface
class Menu(ABC):
    @abstractmethod
    def print(self) -> None:
        pass
Python

Menu Item Class

Create class “MenuItem” for individual item of the menu.
Inherit form “Menu” abstract clas.
In the “__init__” method accept the link and text string.
In the “print” method print the item(as per requirement).
# MenuItem Class
class MenuItem(Menu):
    def __init__(self, link: str, text: str):
        self.link = link
        self.text = text

    def print(self) -> None:
        print(f"[li][a link='{self.link}']{self.text}[/a][/li]")
Python

Menu Item Group

Create class “MenuParent” for group of menu items.
Inherit form “Menu” abstract clas.
In the “__init__” method initialize attribute “menu_items” which is used to store the list of menu items, in case of a parent child.
Implement method “add_item” and “remove_item” for managing items in the list “menu_items”.
In the “print” method call the “print” method of all items of “menu_list“.
# MenuParent Class
class MenuParent(Menu):
    def __init__(self):
        self.menu_items: List[Menu] = []

    def print(self) -> None:
        print("[ul]")
        for menu_item in self.menu_items:
            menu_item.print()
        print("[/ul]")

    def add_item(self, menu_item: Menu) -> None:
        self.menu_items.append(menu_item)

    def remove_item(self, menu_item: Menu) -> None:
        if menu_item in self.menu_items:
            self.menu_items.remove(menu_item)
Python

Demo

# Demo
def main():
    # Define some menu items
    item1 = MenuItem("http://firstlink.com", "First Item")
    item2 = MenuItem("http://secondlink.com", "Second Item")
    item3 = MenuItem("http://thirdlink.com", "Third Item")

    # Define a group of items
    item_group1 = MenuParent()
    item_group1.add_item(MenuItem("http://group-item-1.com", "First group item"))
    item_group1.add_item(MenuItem("http://group-item-2.com", "Second group item"))
    item_group1.add_item(MenuItem("http://group-item-3.com", "Third group item"))
    item_group1.add_item(MenuItem("http://group-item-4.com", "Fourth group item"))

    item4 = MenuItem("http://item-4.com", "4th Item")

    # Add items to main menu
    main_menu = MenuParent()
    main_menu.add_item(item1)
    main_menu.add_item(item2)
    main_menu.add_item(item3)
    main_menu.add_item(item_group1)
    main_menu.add_item(item4)

    # Print the menu
    main_menu.print()


if __name__ == "__main__":
    main()
Python

Output

Output will be as below-

[ul]
    [li][a link='http://firstlink.com']First Item[/a][/li]
    [li][a link='http://secondlink.com']Second Item[/a][/li]
    [li][a link='http://thirdlink.com']Third Item[/a][/li]
    [ul]
        [li][a link='http://group-item-1.com']First group item[/a][/li]
        [li][a link='http://group-item-2.com']Second group item[/a][/li]
        [li][a link='http://group-item-3.com']Third group item[/a][/li]
        [li][a link='http://group-item-4.com']Fourth group item[/a][/li]
    [ul]
    [li][a link='http://item-4.com']4th Item[/a][/li]
[ul]
Plaintext

Example #4: File System

We can use the Composite pattern to show items of a file system.

File System Interface

Create abstract class “FileSystem“.
Define abstract method “print” used to print file or directory name.
from abc import ABC, abstractmethod
from typing import List


# FileSystem Interface
class FileSystem(ABC):
    @abstractmethod
    def print(self, indent: int = 0) -> None:
        pass
Python

File Class [Single File]

Create class “File“, which is used for a single file.
In the “__init__” method accept and set file name and size.
Inherit from “FileSystem“.
In the “print” method definition print the file information.
# File Class
class File(FileSystem):
    def __init__(self, name: str, size: int):
        self.name = name
        self.size = size

    def print(self, indent: int = 0) -> None:
        print(" " * indent + f"File: {self.name} (Size: {self.size} KB)")
Python

Directory Class [File Group]

Create class “Directory“, which is used for a directory.
In the “__init__” method initialize a variable named “name“, “contents” to store all the files of a directory.
Inherit from “FileSystem“.
In the “print” method definition print directory info, and then call the “print” method of all the items in “contents“.
# Directory Class
class Directory(FileSystem):
    def __init__(self, name: str):
        self.name = name
        self.contents: List[FileSystemEntity] = []

    def print(self, indent: int = 0) -> None:
        print(" " * indent + f"Directory: {self.name}")
        for entity in self.contents:
            entity.print(indent + 2)

    def add_entity(self, entity: FileSystem) -> None:
        self.contents.append(entity)

    def remove_entity(self, entity: FileSystem) -> None:
        if entity in self.contents:
            self.contents.remove(entity)
Python

Demo

# Demo
def main():
    # Create some files
    file1 = File("file1.txt", 128)
    file2 = File("file2.txt", 1024)
    file3 = File("file3.txt", 2048)

    # Create a directory and add files
    dir1 = Directory("dir1")
    dir1.add_entity(file1)
    dir1.add_entity(file2)

    # Create a nested directory
    nested_dir = Directory("nested_dir")
    nested_dir.add_entity(file3)

    # Add the nested directory to dir1
    dir1.add_entity(nested_dir)

    # Create the root directory and add dir1 to it
    root_dir = Directory("root")
    root_dir.add_entity(dir1)
    root_dir.add_entity(File("root_file.txt", 256))

    # Print the entire file structure
    root_dir.print()


if __name__ == "__main__":
    main()
Python

Output

Directory: root
  Directory: dir1
    File: file1.txt (Size: 128 KB)
    File: file2.txt (Size: 1024 KB)
    Directory: nested_dir
      File: file3.txt (Size: 2048 KB)
  File: root_file.txt (Size: 256 KB)
Plaintext

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Composite pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.