TUTORIAL SERIES

Design Patterns in Python: Composite

Harmony of Hierarchies

Amir Lavasani
6 min readDec 11, 2023

Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. Thatā€™s precisely what design patterns provide. In this series, weā€™ll explore what these patterns are and how they can elevate your coding skills.

Understanding the Composite Pattern

What is the Composite Design Pattern?

The Composite Design Pattern is a structural approach that organizes objects into tree-like structures, uniformly treating individual objects and compositions.

When to Use the Builder Pattern:

The Composite Pattern suits tasks needing a tree-like structure where elements and collections are handled similarly.

  1. Hierarchical Structures: Employ when creating tree-like systems where elements share common handling.
  2. Complex Relationships: Ideal for managing intricate connections among objects, and simplifying software structures.
  3. Unified Element Management: Use to streamline handling various elements uniformly within software hierarchies.

Practical Example: JSON Parser

A JSON parser reads JSON data, which looks like nested trees. The Composite Pattern fits perfectly here. JSON files have objects inside objects or lists, making a tree-like setup.

With the Composite Pattern, the parser deals with every piece of data in the same way, whether itā€™s an individual item or a group. This makes navigating through the JSON structure easy for the parser, simplifying how it reads and understands the data.

We are going to implement a simple JSON parser to demonstrate how Composite pattern works

Dall-E generated image with the following concept: An abstract tree with branches and leaves, each branch representing a Composite and leaves symbolizing individual elements.

Terminology and Key Components

To understand the Composite Pattern, letā€™s break down its vital components:

  1. Component Interface: This sets the rules for all elements involved, defining common behaviors or attributes.
  2. Leaf: Represents individual objects that donā€™t contain other elements, functioning as the basic building blocks.
  3. Composite: Acts as a container that can hold both Leaf and other Composite instances, forming the structure.
Composite design pattern structure diagram. Image from refactoring.guru

Composite Pattern Implementation in Python

Here is an abstract implementation of the Composite pattern using Python.

Step 1: Component Interface

from abc import ABC, abstractmethod

# Step 1: Define the Component Interface
class Component(ABC):
"""The Component interface sets the common method for all components."""

@abstractmethod
def operation(self):
"""The operation method needs to be implemented by Leaf and Composite classes."""
pass

Step 2: Leaf

# Step 2: Create Leaf Class
class Leaf(Component):
"""Leaf represents individual objects that donā€™t contain other elements."""

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

def operation(self):
"""Operation method for Leaf."""
return f"Leaf: {self.name}"

Step 3: Composite

# Step 3: Create Composite Class
class Composite(Component):
"""Composite acts as a container that can hold both Leaf and other Composite instances."""

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

def add(self, component):
"""Method to add elements to the Composite."""
self.children.append(component)

def remove(self, component):
"""Method to remove elements from the Composite."""
self.children.remove(component)

def operation(self):
"""Operation method for Composite."""
results = [f"Composite: {self.name}"]
for child in self.children:
results.append(child.operation())
return "\n".join(results)

Step 4: Client Code

# Step 3: Demonstrate the Usage in Main
if __name__ == "__main__":
# Creating Leaf objects
leaf1 = Leaf("Leaf 1")
leaf2 = Leaf("Leaf 2")
leaf3 = Leaf("Leaf 3")

# Creating Composite objects
composite1 = Composite("Composite 1")
composite2 = Composite("Composite 2")

# Adding Leaf elements to Composite 1
composite1.add(leaf1)
composite1.add(leaf2)

# Adding Composite 1 and Leaf 3 to Composite 2
composite2.add(composite1)
composite2.add(leaf3)

# Displaying the structure and executing operations
print(composite2.operation())


# The output of the client code

# Composite: Composite 2
# Composite: Composite 1
# Leaf: Leaf 1
# Leaf: Leaf 2
# Leaf: Leaf 3

JSON Parser using Composite Pattern

In this section, we build a basic JSON parser using the Composite Pattern. It has two main parts: first, defining how each piece of the JSON structure behaves (like objects and values); and second, parsing a given JSON string into these structured pieces.

The code creates nodes representing the JSON elements, such as objects or leaf values, and then showcases this parsed structure.

Step 1: Define the Component Interface

Establishes a shared method for JSON components through an abstract interface.

from abc import ABC, abstractmethod

class JSONComponent(ABC):
"""The Component interface sets the common method for all components in the JSON parser."""

@abstractmethod
def parse(self):
"""The parse method needs to be implemented by Leaf and Composite classes."""
pass

Step 2: Create Leaf and Composite Classes

Defines classes for individual JSON elements and their container, facilitating hierarchical structures.

class JSONLeaf(JSONComponent):
"""Leaf represents individual elements in the JSON structure."""

def __init__(self, key, value):
self.key = key
self.value = value

def parse(self):
"""Parsing method for Leaf."""
return f"Parsing JSON Leaf: {self.key}: {self.value}"


class JSONObject(JSONComponent):
"""Object represents JSON objects that can contain other JSON elements."""

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

def add(self, component):
"""Method to add elements to the JSON Object."""
self.children.append(component)

def remove(self, component):
"""Method to remove elements from the JSON Object."""
self.children.remove(component)

def parse(self):
"""Parsing method for JSON Object."""
results = []
for child in self.children:
results.append(child.parse())
return "\n".join(results)

Step 3: Parse JSON String and Create Nodes

Parses a given JSON string, creating nodes that represent the elements within the JSON structure.

import json

def parse_json(json_string):
# Parse JSON string
data = json.loads(json_string)

# Create root JSON Object
root = JSONObject()

# Create nodes based on JSON structure
for key, value in data.items():
if isinstance(value, dict):
obj = JSONObject()
for k, v in value.items():
leaf = JSONLeaf(k, v)
obj.add(leaf)
root.add(obj)
else:
leaf = JSONLeaf(key, value)
root.add(leaf)

return root

if __name__ == "__main__":
# Sample JSON string
sample_json = '{"name": "John Doe", "age": 30, "address": {"city": "New York", "zip": 10001}}'

# Parsing JSON string and creating nodes
json_structure = parse_json(sample_json)

# Displaying the structure and executing parsing
print(json_structure.parse())

GitHub Repo šŸŽ‰

Explore all code examples and design pattern implementations on GitHub!

Best Practices and Considerations

Guidelines for Effectively Implementing the Composite Pattern

  • Clear Abstractions: Define clear abstractions for components to ensure a consistent interface.
  • Recursive Operations: Leverage recursion for operations across the composite structure to maintain consistency.
  • Usage of Interfaces: Implement interfaces to ensure uniformity among components.

Caveats and Points to Consider

  • Complexity Management: Large hierarchical structures might become complex to manage.
  • Runtime Efficiency: Recursive operations can impact performance, needing optimization.
  • Modification Challenges: Dynamically modifying the structure might require careful handling to maintain integrity.
Dall-E generated image with the following concept: An intricate fractal designs that repeat at various scales, resembling the hierarchical nature of Composite structures.

Composite Patternā€™s Relations with Other Patterns

The Composite pattern exhibits diverse relationships with several other design patterns, fostering synergy and enhancing its functionality within complex software architectures.

Builder Pattern Integration

  • Usage in Complex Composite Trees: Employ Builder during the creation of intricate Composite trees, enabling recursive construction steps for streamlined tree generation.

Chain of Responsibility Collaboration

  • Collaborative Usage with Composite: When a leaf component receives a request, it can traverse through the chain of parent components within a Composite structure, reaching the root of the object tree.

Utilization of Iterators

  • Traversing Composite Trees: Iterators are instrumental in traversing Composite structures efficiently, allowing seamless navigation through the hierarchical arrangement.

Visitor Patternā€™s Role

  • Execution Across Composite Trees: Leverage Visitor pattern to execute operations uniformly across the entire Composite structure, facilitating comprehensive operations.

Flyweight Optimization

  • Shared Leaf Nodes as Flyweights: Implement shared leaf nodes within the Composite tree as Flyweights to optimize memory usage, conserving RAM resources.

Comparison with Decorator Pattern

  • Structural Similarity: Composite and Decorator patterns share structural resemblances, relying on recursive composition for managing numerous objects.
  • Distinguishing Characteristics: Decorator extends responsibilities to a single child component, whereas Composite aggregates its childrenā€™s results without altering their responsibilities.

Cooperative Nature of Composite and Decorator

  • Decorator in Composite Extensions: Utilize the Decorator to expand specific object behavior within the Composite tree, showcasing their cooperative potential.

Synergy with Prototype Pattern

  • Beneficial Integration with Prototype: In designs heavily employing Composite and Decorator, Prototype pattern aids by enabling the cloning of complex structures, bypassing the need for reconstructing them entirely.

Conclusion

The Composite Pattern simplifies complex structures in software. It ensures consistency and easy handling of elements in tree-like layouts.

In the JSON parser example, the Composite Pattern efficiently manages JSONā€™s nested data. It simplifies how individual elements and collections are represented, showcasing the patternā€™s practicality in handling hierarchical structures.

I hope you enjoyed the Composite pattern exploration šŸ™Œ Happy coding! šŸ‘Øā€šŸ’»

Next on the Series šŸš€

Read More šŸ“œ

The Series šŸ§­

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software (Book)
  2. refactoring.guru Composite
  3. Head First Design Patterns (Book)
  4. The Composite Pattern
  5. Composite in Python

--

--

Amir Lavasani

I delve into machine learning šŸ¤– and software architecture šŸ° to enhance my expertise while sharing insights with Medium readers. šŸ“ƒ