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-
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()
PythonOutput:
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
PlaintextExamples
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]
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
PythonBike Class [Item Class]
# 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...")
PythonPlane Class [Item Class]
# 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...")
PythonCar Class [Item Class]
# 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...")
PythonTransport Group Class [Composite Class]
# 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)
PythonDemo
# 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()
PythonOutput
-----------------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...
PlaintextExample #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]
from abc import ABC, abstractmethod
from typing import List
# Player Interface
class Player(ABC):
@abstractmethod
def print_details(self) -> None:
pass
PythonBasketball Player Class [Item Class]
# 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}")
PythonFootball Player Class [Item Class]
# 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}")
PythonCricket Player Class [Item Class]
# 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}")
PythonPlayer Group
# 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)
PythonDemo
# 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()
PythonOutput
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
PlaintextExample #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
from abc import ABC, abstractmethod
from typing import List
# Menu Interface
class Menu(ABC):
@abstractmethod
def print(self) -> None:
pass
PythonMenu Item Class
# 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]")
PythonMenu Item Group
# 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)
PythonDemo
# 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()
PythonOutput
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]
PlaintextExample #4: File System
We can use the Composite pattern to show items of a file system.
File System Interface
from abc import ABC, abstractmethod
from typing import List
# FileSystem Interface
class FileSystem(ABC):
@abstractmethod
def print(self, indent: int = 0) -> None:
pass
PythonFile Class [Single File]
# 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)")
PythonDirectory Class [File Group]
# 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)
PythonDemo
# 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()
PythonOutput
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)
PlaintextSource Code
Use the following link to get the source code:
Example | Source Code Link |
---|---|
Example #1: Transport List | GitHub |
Example #2: Player Group | GitHub |
Example #3: Menu | GitHub |
Example #4: File System | GitHub |
Other Code Implementations
Use the following links to check Composite pattern implementation in other programming languages.