Composite Design Pattern (Structural)
Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.
Problem
Section titled βProblemβUsing the Composite pattern makes sense only when the core model of your app can be represented as a tree.
For example, imagine you have two types of objects: Products and Boxes. A Box can contain several Products as well as a number of smaller Boxes. These little Boxes can also hold some Products or even smaller Boxes, and so on.
Say you decide to create an ordering system that uses these classes. Orders could contain simple products without any packaging, as well as boxes stuffed with productsβ¦and other boxes. How would you determine the total price of such an order?
Solution
Section titled βSolutionβThe Composite pattern suggests that you work with Products and Boxes through a common interface which declares a method for calculating the total price.
For a product, itβd simply return the productβs price. For a box, itβd go over each item the box contains, ask its price and then return a total for this box. If one of these items were a smaller box, that box would also begin going over its contents and so on, until the prices of all inner components were calculated.
Real-World Analogy
Section titled βReal-World AnalogyβAn Army is a great example of a composite structure. An army consists of several divisions; a division is a set of brigades, and a brigade consists of platoons, which can be broken down into squads. Finally, a squad is a small group of real soldiers. Orders are given at the top of the hierarchy and passed down through each level until every soldier knows what has to be done.
Structure
Section titled βStructureβ- Component: The interface describes operations that are common to both simple and complex elements of the tree.
- Leaf: A basic element of a tree that doesnβt have sub-elements.
- Composite (Container): An element that has sub-elements: leaves or other composites. A container doesnβt know the concrete classes of its children.
- Client: Works with all elements through the component interface.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class Component {
<<interface>>
+execute()
}
class Leaf {
+execute()
}
class Composite {
-children: List<Component>
+add(c: Component)
+remove(c: Component)
+execute()
}
Component <|-- Leaf
Component <|-- Composite
Composite o-- Component : children
Pseudocode
Section titled βPseudocodeβ# The component interface declares common operations for both# simple and complex objects of a composition.interface Graphic: method move(x, y) method draw()
# The leaf class represents end objects of a composition.class Dot implements Graphic: field x, y constructor Dot(x, y) { ... } method move(x, y) { this.x += x, this.y += y } method draw() { # Draw a dot at X and Y. }
# The composite class represents complex components that may# have children.class CompoundGraphic implements Graphic: field children: array of Graphic
method add(child: Graphic) { add child to children } method remove(child: Graphic) { remove child from children }
method move(x, y): foreach (child in children): child.move(x, y)
method draw(): # 1. For each child element: # 2. Draw the element.Applicability
Section titled βApplicabilityβ- Tree-like structures: Use the pattern when you have to implement a tree-like object structure.
- Uniformity: Use the pattern when you want the client code to treat both simple and complex elements uniformly.
How to Implement
Section titled βHow to Implementβ- Make sure that the core model of your app can be represented as a tree structure.
- Declare the component interface with a list of methods that make sense for both simple and complex components.
- Create a leaf class to represent simple elements.
- Create a container class to represent complex elements, including an array field for storing references to sub-elements.
- Finally, define the methods for adding and removal of child elements in the container.
Pros and Cons
Section titled βPros and ConsβPros:
- Polymorphism: You can work with complex tree structures more conveniently using recursion.
- Open/Closed Principle: You can introduce new element types into the app without breaking existing code.
Cons:
- Over-generalization: It might be difficult to provide a common interface for classes whose functionality differs too much.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- Chain of Responsibility is often used in conjunction with Composite. In this case, when a leaf component gets a request, it may pass it through the chain of all of the parent components down to the root of the object tree.
- You can use Iterators to traverse Composite trees.
- You can use Visitor to execute an operation over an entire Composite tree.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Componentpublic abstract class FileSystemItem{ public string Name { get; }
protected FileSystemItem(string name) { Name = name; }
public abstract void Display(int depth);}
// Leafpublic class File : FileSystemItem{ public File(string name) : base(name) {}
public override void Display(int depth) { Console.WriteLine(new string('-', depth) + Name); }}
// Compositepublic class Directory : FileSystemItem{ private readonly List<FileSystemItem> _items = new();
public Directory(string name) : base(name) {}
public void Add(FileSystemItem item) => _items.Add(item);
public override void Display(int depth) { Console.WriteLine(new string('-', depth) + Name);
foreach (var item in _items) item.Display(depth + 2); }}
// Usageclass Program{ static void Main() { var root = new Directory("root"); root.Add(new File("file1.txt")); root.Add(new File("file2.txt"));
var folder = new Directory("docs"); folder.Add(new File("resume.pdf")); folder.Add(new File("notes.txt"));
root.Add(folder);
root.Display(1); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom __future__ import annotationsfrom abc import ABC, abstractmethod
class Component(ABC): @abstractmethod def operation(self) -> str: pass
class Leaf(Component): def operation(self) -> str: return "Leaf"
class Composite(Component): def __init__(self) -> None: self._children: list[Component] = []
def add(self, component: Component) -> None: self._children.append(component)
def operation(self) -> str: results = [] for child in self._children: results.append(child.operation()) return f"Branch({'+'.join(results)})"
if __name__ == "__main__": tree = Composite() branch1 = Composite() branch1.add(Leaf()) branch1.add(Leaf())
tree.add(branch1) tree.add(Leaf())
print(f"RESULT: {tree.operation()}")Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <string>#include <vector>#include <memory>
// ============================// Component// ============================class FileSystemItem {protected: std::string name;
public: FileSystemItem(const std::string& n) : name(n) {} virtual ~FileSystemItem() = default;
virtual void Display(int depth) const = 0;};
// ============================// Leaf// ============================class File : public FileSystemItem {public: File(const std::string& n) : FileSystemItem(n) {}
void Display(int depth) const override { std::cout << std::string(depth, '-') << name << std::endl; }};
// ============================// Composite// ============================class Directory : public FileSystemItem {private: std::vector<std::unique_ptr<FileSystemItem>> items;
public: Directory(const std::string& n) : FileSystemItem(n) {}
void Add(std::unique_ptr<FileSystemItem> item) { items.push_back(std::move(item)); }
void Display(int depth) const override { std::cout << std::string(depth, '-') << name << std::endl;
for (const auto& item : items) { item->Display(depth + 2); } }};
// ============================// Usage// ============================int main() { auto root = std::make_unique<Directory>("root"); root->Add(std::make_unique<File>("file1.txt")); root->Add(std::make_unique<File>("file2.txt"));
auto docs = std::make_unique<Directory>("docs"); docs->Add(std::make_unique<File>("resume.pdf")); docs->Add(std::make_unique<File>("notes.txt"));
root->Add(std::move(docs));
root->Display(1);
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Componentpublic abstract class FileSystemItem {
protected final String name;
protected FileSystemItem(String name) { this.name = name; }
public abstract void display(int depth);}
// Leafpublic class File extends FileSystemItem {
public File(String name) { super(name); }
@Override public void display(int depth) { System.out.println("-".repeat(depth) + name); }}
// Compositeimport java.util.ArrayList;import java.util.List;
public class Directory extends FileSystemItem {
private final List<FileSystemItem> items = new ArrayList<>();
public Directory(String name) { super(name); }
public void add(FileSystemItem item) { items.add(item); }
@Override public void display(int depth) { System.out.println("-".repeat(depth) + name);
for (FileSystemItem item : items) { item.display(depth + 2); } }}
// Usagepublic class Program { public static void main(String[] args) { Directory root = new Directory("root"); root.add(new File("file1.txt")); root.add(new File("file2.txt"));
Directory docs = new Directory("docs"); docs.add(new File("resume.pdf")); docs.add(new File("notes.txt"));
root.add(docs);
root.display(1); }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Component (abstract)class FileSystemItem { constructor(name) { if (new.target === FileSystemItem) { throw new Error("FileSystemItem is abstract and cannot be instantiated directly"); } this.name = name; }
display(depth) { throw new Error("display() must be implemented"); }}
// Leafclass File extends FileSystemItem { constructor(name) { super(name); }
display(depth) { console.log("-".repeat(depth) + this.name); }}
// Compositeclass Directory extends FileSystemItem { constructor(name) { super(name); this.items = []; }
add(item) { this.items.push(item); }
display(depth) { console.log("-".repeat(depth) + this.name);
for (const item of this.items) { item.display(depth + 2); } }}
// Usageconst root = new Directory("root");root.add(new File("file1.txt"));root.add(new File("file2.txt"));
const docs = new Directory("docs");docs.add(new File("resume.pdf"));docs.add(new File("notes.txt"));
root.add(docs);
root.display(1);Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Component' ============================Public MustInherit Class FileSystemItem Public ReadOnly Property Name As String
Protected Sub New(name As String) Me.Name = name End Sub
Public MustOverride Sub Display(depth As Integer)End Class
' ============================' Leaf' ============================Public Class FileItem Inherits FileSystemItem
Public Sub New(name As String) MyBase.New(name) End Sub
Public Overrides Sub Display(depth As Integer) Console.WriteLine(New String("-"c, depth) & Name) End SubEnd Class
' ============================' Composite' ============================Public Class DirectoryItem Inherits FileSystemItem
Private ReadOnly _items As New List(Of FileSystemItem)()
Public Sub New(name As String) MyBase.New(name) End Sub
Public Sub Add(item As FileSystemItem) _items.Add(item) End Sub
Public Overrides Sub Display(depth As Integer) Console.WriteLine(New String("-"c, depth) & Name)
For Each item In _items item.Display(depth + 2) Next End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim root As New DirectoryItem("root") root.Add(New FileItem("file1.txt")) root.Add(New FileItem("file2.txt"))
Dim folder As New DirectoryItem("docs") folder.Add(New FileItem("resume.pdf")) folder.Add(New FileItem("notes.txt"))
root.Add(folder)
root.Display(1) End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Componenttrait FileSystemItem { fn display(&self, depth: usize); fn name(&self) -> &str;}
// Leafstruct File { name: String,}
impl File { fn new(name: &str) -> Self { Self { name: name.to_string() } }}
impl FileSystemItem for File { fn display(&self, depth: usize) { println!("{}{}", "-".repeat(depth), self.name); }
fn name(&self) -> &str { &self.name }}
// Compositestruct Directory { name: String, items: Vec<Box<dyn FileSystemItem>>,}
impl Directory { fn new(name: &str) -> Self { Self { name: name.to_string(), items: Vec::new(), } }
fn add(&mut self, item: Box<dyn FileSystemItem>) { self.items.push(item); }}
impl FileSystemItem for Directory { fn display(&self, depth: usize) { println!("{}{}", "-".repeat(depth), self.name);
for item in &self.items { item.display(depth + 2); } }
fn name(&self) -> &str { &self.name }}
// Usagefn main() { let mut root = Directory::new("root"); root.add(Box::new(File::new("file1.txt"))); root.add(Box::new(File::new("file2.txt")));
let mut docs = Directory::new("docs"); docs.add(Box::new(File::new("resume.pdf"))); docs.add(Box::new(File::new("notes.txt")));
root.add(Box::new(docs));
root.display(1);}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Componentabstract class FileSystemItem{ protected string $name;
public function __construct(string $name) { $this->name = $name; }
abstract public function display(int $depth): void;}
// Leafclass File extends FileSystemItem{ public function display(int $depth): void { echo str_repeat('-', $depth) . $this->name . "\n"; }}
// Compositeclass Directory extends FileSystemItem{ /** @var FileSystemItem[] */ private array $items = [];
public function add(FileSystemItem $item): void { $this->items[] = $item; }
public function display(int $depth): void { echo str_repeat('-', $depth) . $this->name . "\n";
foreach ($this->items as $item) { $item->display($depth + 2); } }}
// Usage$root = new Directory("root");$root->add(new File("file1.txt"));$root->add(new File("file2.txt"));
$folder = new Directory("docs");$folder->add(new File("resume.pdf"));$folder->add(new File("notes.txt"));
$root->add($folder);
$root->display(1);Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt" "strings")
// Componenttype FileSystemItem interface { Display(depth int) Name() string}
// Leaftype File struct { name string}
func NewFile(name string) *File { return &File{name: name}}
func (f *File) Name() string { return f.name}
func (f *File) Display(depth int) { fmt.Println(strings.Repeat("-", depth) + f.name)}
// Compositetype Directory struct { name string items []FileSystemItem}
func NewDirectory(name string) *Directory { return &Directory{name: name}}
func (d *Directory) Name() string { return d.name}
func (d *Directory) Add(item FileSystemItem) { d.items = append(d.items, item)}
func (d *Directory) Display(depth int) { fmt.Println(strings.Repeat("-", depth) + d.name) for _, item := range d.items { item.Display(depth + 2) }}
// Usagefunc main() { root := NewDirectory("root") root.Add(NewFile("file1.txt")) root.Add(NewFile("file2.txt"))
docs := NewDirectory("docs") docs.Add(NewFile("resume.pdf")) docs.Add(NewFile("notes.txt"))
root.Add(docs)
root.Display(1)}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Componentclass FileSystemItemattr_reader :name
def initialize(name) @name = nameend
def display(depth) raise NotImplementedError, "Subclasses must implement display"endend
# Leafclass File < FileSystemItemdef display(depth) puts "#{'-' * depth}#{name}"endend
# Compositeclass Directory < FileSystemItemdef initialize(name) super(name) @items = []end
def add(item) @items << itemend
def display(depth) puts "#{'-' * depth}#{name}" @items.each { |item| item.display(depth + 2) }endend
# Usageroot = Directory.new("root")root.add(File.new("file1.txt"))root.add(File.new("file2.txt"))
folder = Directory.new("docs")folder.add(File.new("resume.pdf"))folder.add(File.new("notes.txt"))
root.add(folder)
root.display(1)Code Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Component (Abstract)############################package FileSystemItem;
sub new { my ($class, $name) = @_; return bless { name => $name }, $class;}
sub display { die "display() must be implemented by subclass";}
############################# Leaf############################package File;our @ISA = ('FileSystemItem');
sub new { my ($class, $name) = @_; return $class->SUPER::new($name);}
sub display { my ($self, $depth) = @_; print ('-' x $depth) . $self->{name} . "\n";}
############################# Composite############################package Directory;our @ISA = ('FileSystemItem');
sub new { my ($class, $name) = @_; return bless { name => $name, items => [] }, $class;}
sub add { my ($self, $item) = @_; push @{ $self->{items} }, $item;}
sub display { my ($self, $depth) = @_; print ('-' x $depth) . $self->{name} . "\n";
foreach my $item (@{ $self->{items} }) { $item->display($depth + 2); }}
############################# Usage############################package main;
my $root = Directory->new("root");$root->add(File->new("file1.txt"));$root->add(File->new("file2.txt"));
my $folder = Directory->new("docs");$folder->add(File->new("resume.pdf"));$folder->add(File->new("notes.txt"));
$root->add($folder);
$root->display(1);Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Component (abstract)abstract class FileSystemItem { name: string;
constructor(name: string) { this.name = name; }
abstract display(depth: number): void;}
// Leafclass File extends FileSystemItem { constructor(name: string) { super(name); }
display(depth: number): void { console.log("-".repeat(depth) + this.name); }}
// Compositeclass Directory extends FileSystemItem { private items: FileSystemItem[] = [];
constructor(name: string) { super(name); }
add(item: FileSystemItem): void { this.items.push(item); }
display(depth: number): void { console.log("-".repeat(depth) + this.name);
for (const item of this.items) { item.display(depth + 2); } }}
// Usageconst root = new Directory("root");root.add(new File("file1.txt"));root.add(new File("file2.txt"));
const folder = new Directory("docs");folder.add(new File("resume.pdf"));folder.add(new File("notes.txt"));
root.add(folder);
root.display(1);