Composite
The composite pattern composes objects into tree structures to represent these structures as though they were individual objects. This means that a client can treat individual objects and compositions thereof uniformly.
TL;DR
Lets clients treat groups of objects as through they were an individual object. Only works with tree-like structures, such as file systems.
Problem
Suppose you are implementing a file system, which contains both individual files and directories, which themselves can contain files or directories, and you’re trying to determine the size of a particular directory. It would be difficult to create readable loops that can traverse the file system structure while not knowing what type of object they are dealing with - this leads to massive switch cases and the requirement for a priori knowledge about the details of the file system itself
Solution
The Composite pattern suggests that the implementation should use Components and Composites to manage the complexity, where Components are like leaf nodes, and Composites are nodes with other Components or Composites. In doing so, simply ensure that both files and directories, to follow the problem above, can be treated in the same way via a common interface.
A Component can be created as a common interface that has a get_size() method; this method will then either return the size of the current file or, if a directory, return the size of all objects contained within. This solution means that along the entire file system tree every object can be manipulated using the same method, without needing to know beforehand whether it is a file or a directory.
from abc import ABC, abstractmethod
# Component - Abstract base class
class FileSystemComponent(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def get_size(self):
pass
@abstractmethod
def display(self, indent=0):
pass
# Leaf - Individual files
class File(FileSystemComponent):
def __init__(self, name, size):
super().__init__(name)
self.size = size
def get_size(self):
return self.size
def display(self, indent=0):
print(" " * indent + f"📄 {self.name} ({self.size} KB)")
# Composite - Directories that can contain files and other directories
class Directory(FileSystemComponent):
def __init__(self, name):
super().__init__(name)
self.children = []
def add(self, component):
self.children.append(component)
def remove(self, component):
self.children.remove(component)
def get_size(self):
total_size = 0
for child in self.children:
total_size += child.get_size()
return total_size
def display(self, indent=0):
print(" " * indent + f"📁 {self.name}/")
for child in self.children:
child.display(indent + 1)
# Usage example
if __name__ == "__main__":
# Create files (leaves)
file1 = File("document.txt", 25)
file2 = File("image.jpg", 150)
file3 = File("script.py", 12)
file4 = File("data.csv", 75)
# Create directories (composites)
root = Directory("home")
documents = Directory("documents")
projects = Directory("projects")
python_project = Directory("my_python_app")
# Build the tree structure
documents.add(file1)
documents.add(file2)
python_project.add(file3)
python_project.add(file4)
projects.add(python_project)
root.add(documents)
root.add(projects)
# Display the entire structure
print("File System Structure:")
root.display()
print(f"\nTotal size of root directory: {root.get_size()} KB")
print(f"Size of documents directory: {documents.get_size()} KB")
print(f"Size of python project: {python_project.get_size()} KB")