Composite Pattern
Composite Pattern: Treating Individual Objects and Compositions Uniformly
Section titled “Composite Pattern: Treating Individual Objects and Compositions Uniformly”Now let’s explore the Composite Pattern - a powerful structural design pattern that lets you compose objects into tree structures and work with them uniformly.
Why Composite Pattern?
Section titled “Why Composite Pattern?”Imagine you’re organizing files and folders on your computer. A folder can contain files AND other folders. When you want to get the total size, you need to treat both files and folders the same way - folders recursively sum up their contents. The Composite Pattern makes this possible by treating individual objects (files) and compositions (folders) uniformly.
The Composite Pattern composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.
What’s the Use of Composite Pattern?
Section titled “What’s the Use of Composite Pattern?”The Composite Pattern is useful when:
- You want to represent part-whole hierarchies - Objects that contain other objects
- You want clients to ignore - The difference between individual objects and compositions
- You want to treat objects uniformly - Same interface for leaves and composites
- You have tree structures - Hierarchical data that needs uniform treatment
- You want recursive operations - Operations that work on both leaves and composites
What Happens If We Don’t Use Composite Pattern?
Section titled “What Happens If We Don’t Use Composite Pattern?”Without the Composite Pattern, you might:
- Type checking everywhere - Need to check if object is leaf or composite
- Different interfaces - Leaves and composites have different methods
- Complex client code - Clients need to handle leaves and composites differently
- Code duplication - Similar logic repeated for leaves and composites
- Hard to extend - Adding new types requires changes everywhere
Simple Example: The File System
Section titled “Simple Example: The File System”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Composite Pattern works in practice - showing how leaves and composites work uniformly:
The Problem
Section titled “The Problem”You’re building a file system. You have files (individual objects) and folders (containers). Without Composite Pattern:
# ❌ Without Composite Pattern - Different interfaces!
class File: """File - individual object""" def __init__(self, name: str, size: int): self.name = name self.size = size
def get_size(self) -> int: return self.size
class Folder: """Folder - container""" def __init__(self, name: str): self.name = name self.files = [] # Only files, not folders! self.folders = [] # Separate list for folders
def add_file(self, file: File): self.files.append(file)
def add_folder(self, folder: 'Folder'): self.folders.append(folder)
def get_size(self) -> int: total = 0 # Handle files for file in self.files: total += file.get_size() # Handle folders separately for folder in self.folders: total += folder.get_size() # Recursive return total
# Problem: Client needs to know about files vs folders!def get_total_size(folder: Folder) -> int: total = 0 # Different handling for files and folders for file in folder.files: total += file.get_size() for subfolder in folder.folders: total += get_total_size(subfolder) # Recursive call return total
# Problems:# - Different interfaces for File and Folder# - Client needs to check type (is it file or folder?)# - Can't treat files and folders uniformly# - Hard to extend (add new types)// ❌ Without Composite Pattern - Different interfaces!
public class File { // File - individual object private String name; private int size;
public File(String name, int size) { this.name = name; this.size = size; }
public int getSize() { return size; }}
public class Folder { // Folder - container private String name; private List<File> files; // Only files, not folders! private List<Folder> folders; // Separate list for folders
public Folder(String name) { this.name = name; this.files = new ArrayList<>(); this.folders = new ArrayList<>(); }
public void addFile(File file) { files.add(file); }
public void addFolder(Folder folder) { folders.add(folder); }
public int getSize() { int total = 0; // Handle files for (File file : files) { total += file.getSize(); } // Handle folders separately for (Folder folder : folders) { total += folder.getSize(); // Recursive } return total; }}
// Problem: Client needs to know about files vs folders!public static int getTotalSize(Folder folder) { int total = 0; // Different handling for files and folders for (File file : folder.files) { total += file.getSize(); } for (Folder subfolder : folder.folders) { total += getTotalSize(subfolder); // Recursive call } return total;}
// Problems:// - Different interfaces for File and Folder// - Client needs to check type (is it file or folder?)// - Can't treat files and folders uniformly// - Hard to extend (add new types)// ❌ Without Composite Pattern - Different interfaces!
class File { /** File - individual object */ private name: string; private size: number;
constructor(name: string, size: number) { this.name = name; this.size = size; }
getSize(): number { return this.size; }}
class Folder { /** Folder - container */ private name: string; private files: File[] = []; // Only files, not folders! private folders: Folder[] = []; // Separate list for folders
constructor(name: string) { this.name = name; }
addFile(file: File): void { this.files.push(file); }
addFolder(folder: Folder): void { this.folders.push(folder); }
getSize(): number { let total = 0; // Handle files for (const file of this.files) { total += file.getSize(); } // Handle folders separately for (const folder of this.folders) { total += folder.getSize(); // Recursive } return total; }
getFiles(): File[] { return this.files; }
getFolders(): Folder[] { return this.folders; }}
// Problem: Client needs to know about files vs folders!function getTotalSize(folder: Folder): number { let total = 0; // Different handling for files and folders for (const file of folder.getFiles()) { total += file.getSize(); } for (const subfolder of folder.getFolders()) { total += getTotalSize(subfolder); // Recursive call } return total;}
// Problems:// - Different interfaces for File and Folder// - Client needs to check type (is it file or folder?)// - Can't treat files and folders uniformly// - Hard to extend (add new types)// ❌ Without Composite Pattern - Different interfaces!
#include <iostream>#include <string>#include <vector>
class File {private: std::string name; int size;
public: File(const std::string& name, int size) : name(name), size(size) {}
int getSize() const { return size; }};
class Folder {private: std::string name; std::vector<File> files; // Only files, not folders! std::vector<Folder> folders; // Separate list for folders
public: Folder(const std::string& name) : name(name) {}
void addFile(const File& file) { files.push_back(file); }
void addFolder(const Folder& folder) { folders.push_back(folder); }
int getSize() const { int total = 0; // Handle files for (const auto& file : files) { total += file.getSize(); } // Handle folders separately for (const auto& folder : folders) { total += folder.getSize(); // Recursive } return total; }
const std::vector<File>& getFiles() const { return files; }
const std::vector<Folder>& getFolders() const { return folders; }};
// Problem: Client needs to know about files vs folders!int getTotalSize(const Folder& folder) { int total = 0; // Different handling for files and folders for (const auto& file : folder.getFiles()) { total += file.getSize(); } for (const auto& subfolder : folder.getFolders()) { total += getTotalSize(subfolder); // Recursive call } return total;}
// Problems:// - Different interfaces for File and Folder// - Client needs to check type (is it file or folder?)// - Can't treat files and folders uniformly// - Hard to extend (add new types)// ❌ Without Composite Pattern - Different interfaces!
using System;using System.Collections.Generic;
public class File{ /** File - individual object */ private string name; private int size;
public File(string name, int size) { this.name = name; this.size = size; }
public int GetSize() { return size; }}
public class Folder{ /** Folder - container */ private string name; private List<File> files = new List<File>(); // Only files, not folders! private List<Folder> folders = new List<Folder>(); // Separate list for folders
public Folder(string name) { this.name = name; }
public void AddFile(File file) { files.Add(file); }
public void AddFolder(Folder folder) { folders.Add(folder); }
public int GetSize() { int total = 0; // Handle files foreach (var file in files) { total += file.GetSize(); } // Handle folders separately foreach (var folder in folders) { total += folder.GetSize(); // Recursive } return total; }
public List<File> GetFiles() { return files; }
public List<Folder> GetFolders() { return folders; }}
// Problem: Client needs to know about files vs folders!public static class FileSystemHelper{ public static int GetTotalSize(Folder folder) { int total = 0; // Different handling for files and folders foreach (var file in folder.GetFiles()) { total += file.GetSize(); } foreach (var subfolder in folder.GetFolders()) { total += GetTotalSize(subfolder); // Recursive call } return total; }}
// Problems:// - Different interfaces for File and Folder// - Client needs to check type (is it file or folder?)// - Can't treat files and folders uniformly// - Hard to extend (add new types)// ❌ Without Composite Pattern - Different interfaces!
package main
type BadFile struct { name string size int}
func (f *BadFile) GetSize() int { return f.size }
type BadFolder struct { name string files []*BadFile folders []*BadFolder // Separate slices for each type!}
func (f *BadFolder) AddFile(file *BadFile) { f.files = append(f.files, file) }func (f *BadFolder) AddFolder(folder *BadFolder) { f.folders = append(f.folders, folder) }
func (f *BadFolder) GetSize() int { total := 0 for _, file := range f.files { total += file.GetSize() } for _, folder := range f.folders { total += folder.GetSize() } return total}
// Problems:// - Different interfaces for File and Folder// - Client needs to check type (is it file or folder?)// - Can't treat files and folders uniformly// - Hard to extend (add new types)// The Problemstruct File { name: String,}struct Folder { files: Vec<File>,}impl Folder { fn print_files(&self) { for file in &self.files { println!("{}", file.name); } }}Problems:
- Different interfaces - Files and folders handled differently
- Type checking - Clients need to check if object is file or folder
- Can’t treat uniformly - No common interface
- Hard to extend - Adding new types requires changes everywhere
The Solution: Composite Pattern
Section titled “The Solution: Composite Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define the component interfaceclass FileSystemComponent(ABC): """Component interface - common interface for leaves and composites"""
@abstractmethod def get_size(self) -> int: """Get the size of the component""" pass
@abstractmethod def get_name(self) -> str: """Get the name of the component""" pass
# Step 2: Implement the leaf (File)class File(FileSystemComponent): """File - leaf node in the tree"""
def __init__(self, name: str, size: int): self.name = name self.size = size
def get_size(self) -> int: """Get file size""" return self.size
def get_name(self) -> str: """Get file name""" return self.name
# Step 3: Implement the composite (Folder)class Folder(FileSystemComponent): """Folder - composite node in the tree"""
def __init__(self, name: str): self.name = name self.children: List[FileSystemComponent] = [] # Can contain files or folders!
def add(self, component: FileSystemComponent) -> None: """Add a component (file or folder)""" self.children.append(component)
def remove(self, component: FileSystemComponent) -> None: """Remove a component""" if component in self.children: self.children.remove(component)
def get_size(self) -> int: """Get total size - recursively sums children""" total = 0 for child in self.children: total += child.get_size() # Works for both files and folders! return total
def get_name(self) -> str: """Get folder name""" return self.name
# Usage - Clean and uniform!def main(): # Create files (leaves) file1 = File("document.txt", 100) file2 = File("image.jpg", 200) file3 = File("video.mp4", 500)
# Create folders (composites) root = Folder("Root") documents = Folder("Documents") media = Folder("Media")
# Build the tree structure documents.add(file1) media.add(file2) media.add(file3) root.add(documents) root.add(media)
# Client treats files and folders uniformly! print(f"File size: {file1.get_size()} bytes") print(f"Documents folder size: {documents.get_size()} bytes") print(f"Media folder size: {media.get_size()} bytes") print(f"Root folder size: {root.get_size()} bytes")
print("\n✅ Composite Pattern allows uniform treatment of files and folders!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the component interfaceinterface FileSystemComponent { // Component interface - common interface for leaves and composites int getSize(); String getName();}
// Step 2: Implement the leaf (File)class File implements FileSystemComponent { // File - leaf node in the tree private String name; private int size;
public File(String name, int size) { this.name = name; this.size = size; }
@Override public int getSize() { // Get file size return size; }
@Override public String getName() { // Get file name return name; }}
// Step 3: Implement the composite (Folder)class Folder implements FileSystemComponent { // Folder - composite node in the tree private String name; private List<FileSystemComponent> children; // Can contain files or folders!
public Folder(String name) { this.name = name; this.children = new ArrayList<>(); }
public void add(FileSystemComponent component) { // Add a component (file or folder) children.add(component); }
public void remove(FileSystemComponent component) { // Remove a component children.remove(component); }
@Override public int getSize() { // Get total size - recursively sums children int total = 0; for (FileSystemComponent child : children) { total += child.getSize(); // Works for both files and folders! } return total; }
@Override public String getName() { // Get folder name return name; }}
// Usage - Clean and uniform!public class Main { public static void main(String[] args) { // Create files (leaves) FileSystemComponent file1 = new File("document.txt", 100); FileSystemComponent file2 = new File("image.jpg", 200); FileSystemComponent file3 = new File("video.mp4", 500);
// Create folders (composites) Folder root = new Folder("Root"); Folder documents = new Folder("Documents"); Folder media = new Folder("Media");
// Build the tree structure documents.add(file1); media.add(file2); media.add(file3); root.add(documents); root.add(media);
// Client treats files and folders uniformly! System.out.println("File size: " + file1.getSize() + " bytes"); System.out.println("Documents folder size: " + documents.getSize() + " bytes"); System.out.println("Media folder size: " + media.getSize() + " bytes"); System.out.println("Root folder size: " + root.getSize() + " bytes");
System.out.println("\n✅ Composite Pattern allows uniform treatment of files and folders!"); }}// Step 1: Define the component interfaceinterface FileSystemComponent { /** Component interface - common interface for leaves and composites */ getSize(): number; getName(): string;}
// Step 2: Implement the leaf (File)class File implements FileSystemComponent { /** File - leaf node in the tree */ private name: string; private size: number;
constructor(name: string, size: number) { this.name = name; this.size = size; }
getSize(): number { /** Get file size */ return this.size; }
getName(): string { /** Get file name */ return this.name; }}
// Step 3: Implement the composite (Folder)class Folder implements FileSystemComponent { /** Folder - composite node in the tree */ private name: string; private children: FileSystemComponent[] = []; // Can contain files or folders!
constructor(name: string) { this.name = name; }
add(component: FileSystemComponent): void { /** Add a component (file or folder) */ this.children.push(component); }
remove(component: FileSystemComponent): void { /** Remove a component */ const index = this.children.indexOf(component); if (index > -1) { this.children.splice(index, 1); } }
getSize(): number { /** Get total size - recursively sums children */ let total = 0; for (const child of this.children) { total += child.getSize(); // Works for both files and folders! } return total; }
getName(): string { /** Get folder name */ return this.name; }}
// Usage - Clean and uniform!function main(): void { // Create files (leaves) const file1: FileSystemComponent = new File("document.txt", 100); const file2: FileSystemComponent = new File("image.jpg", 200); const file3: FileSystemComponent = new File("video.mp4", 500);
// Create folders (composites) const root = new Folder("Root"); const documents = new Folder("Documents"); const media = new Folder("Media");
// Build the tree structure documents.add(file1); media.add(file2); media.add(file3); root.add(documents); root.add(media);
// Client treats files and folders uniformly! console.log(`File size: ${file1.getSize()} bytes`); console.log(`Documents folder size: ${documents.getSize()} bytes`); console.log(`Media folder size: ${media.getSize()} bytes`); console.log(`Root folder size: ${root.getSize()} bytes`);
console.log("\n✅ Composite Pattern allows uniform treatment of files and folders!");}
main();#include <iostream>#include <string>#include <vector>#include <memory>
// Step 1: Define the component interfaceclass FileSystemComponent {public: virtual ~FileSystemComponent() = default; // Component interface - common interface for leaves and composites virtual int getSize() const = 0; virtual std::string getName() const = 0;};
// Step 2: Implement the leaf (File)class File : public FileSystemComponent {private: std::string name; int size;
public: File(const std::string& name, int size) : name(name), size(size) {}
int getSize() const override { // Get file size return size; }
std::string getName() const override { // Get file name return name; }};
// Step 3: Implement the composite (Folder)class Folder : public FileSystemComponent {private: std::string name; std::vector<std::shared_ptr<FileSystemComponent>> children; // Can contain files or folders!
public: Folder(const std::string& name) : name(name) {}
void add(std::shared_ptr<FileSystemComponent> component) { // Add a component (file or folder) children.push_back(component); }
void remove(std::shared_ptr<FileSystemComponent> component) { // Remove a component auto it = std::find(children.begin(), children.end(), component); if (it != children.end()) { children.erase(it); } }
int getSize() const override { // Get total size - recursively sums children int total = 0; for (const auto& child : children) { total += child->getSize(); // Works for both files and folders! } return total; }
std::string getName() const override { // Get folder name return name; }};
// Usage - Clean and uniform!int main() { // Create files (leaves) auto file1 = std::make_shared<File>("document.txt", 100); auto file2 = std::make_shared<File>("image.jpg", 200); auto file3 = std::make_shared<File>("video.mp4", 500);
// Create folders (composites) auto root = std::make_shared<Folder>("Root"); auto documents = std::make_shared<Folder>("Documents"); auto media = std::make_shared<Folder>("Media");
// Build the tree structure documents->add(file1); media->add(file2); media->add(file3); root->add(documents); root->add(media);
// Client treats files and folders uniformly! std::cout << "File size: " << file1->getSize() << " bytes" << std::endl; std::cout << "Documents folder size: " << documents->getSize() << " bytes" << std::endl; std::cout << "Media folder size: " << media->getSize() << " bytes" << std::endl; std::cout << "Root folder size: " << root->getSize() << " bytes" << std::endl;
std::cout << "\n✅ Composite Pattern allows uniform treatment of files and folders!" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define the component interfacepublic interface IFileSystemComponent{ /** Component interface - common interface for leaves and composites */ int GetSize(); string GetName();}
// Step 2: Implement the leaf (File)public class File : IFileSystemComponent{ /** File - leaf node in the tree */ private string name; private int size;
public File(string name, int size) { this.name = name; this.size = size; }
public int GetSize() { /** Get file size */ return size; }
public string GetName() { /** Get file name */ return name; }}
// Step 3: Implement the composite (Folder)public class Folder : IFileSystemComponent{ /** Folder - composite node in the tree */ private string name; private List<IFileSystemComponent> children = new List<IFileSystemComponent>(); // Can contain files or folders!
public Folder(string name) { this.name = name; }
public void Add(IFileSystemComponent component) { /** Add a component (file or folder) */ children.Add(component); }
public void Remove(IFileSystemComponent component) { /** Remove a component */ children.Remove(component); }
public int GetSize() { /** Get total size - recursively sums children */ int total = 0; foreach (var child in children) { total += child.GetSize(); // Works for both files and folders! } return total; }
public string GetName() { /** Get folder name */ return name; }}
// Usage - Clean and uniform!class Program{ static void Main() { // Create files (leaves) IFileSystemComponent file1 = new File("document.txt", 100); IFileSystemComponent file2 = new File("image.jpg", 200); IFileSystemComponent file3 = new File("video.mp4", 500);
// Create folders (composites) Folder root = new Folder("Root"); Folder documents = new Folder("Documents"); Folder media = new Folder("Media");
// Build the tree structure documents.Add(file1); media.Add(file2); media.Add(file3); root.Add(documents); root.Add(media);
// Client treats files and folders uniformly! Console.WriteLine($"File size: {file1.GetSize()} bytes"); Console.WriteLine($"Documents folder size: {documents.GetSize()} bytes"); Console.WriteLine($"Media folder size: {media.GetSize()} bytes"); Console.WriteLine($"Root folder size: {root.GetSize()} bytes");
Console.WriteLine("\n✅ Composite Pattern allows uniform treatment of files and folders!"); }}package main
import "fmt"
// Step 1: Define the component interfacetype FileSystemComponent interface { GetSize() int GetName() string}
// Step 2: Leaf (File)type File struct { name string size int}
func NewFile(name string, size int) *File { return &File{name: name, size: size} }func (f *File) GetSize() int { return f.size }func (f *File) GetName() string { return f.name }
// Step 3: Composite (Folder)type Folder struct { name string children []FileSystemComponent // Can contain files or folders!}
func NewFolder(name string) *Folder { return &Folder{name: name} }
func (f *Folder) Add(component FileSystemComponent) { f.children = append(f.children, component)}
func (f *Folder) Remove(component FileSystemComponent) { for i, c := range f.children { if c == component { f.children = append(f.children[:i], f.children[i+1:]...) return } }}
func (f *Folder) GetSize() int { total := 0 for _, child := range f.children { total += child.GetSize() // Works for both files and folders! } return total}
func (f *Folder) GetName() string { return f.name }
func main() { file1 := NewFile("document.txt", 100) file2 := NewFile("image.jpg", 200) file3 := NewFile("video.mp4", 500)
root := NewFolder("Root") documents := NewFolder("Documents") media := NewFolder("Media")
documents.Add(file1) media.Add(file2) media.Add(file3) root.Add(documents) root.Add(media)
// Client treats files and folders uniformly! fmt.Printf("File size: %d bytes\n", file1.GetSize()) fmt.Printf("Documents folder size: %d bytes\n", documents.GetSize()) fmt.Printf("Media folder size: %d bytes\n", media.GetSize()) fmt.Printf("Root folder size: %d bytes\n", root.GetSize())
fmt.Println("\n✅ Composite Pattern allows uniform treatment of files and folders!")}// Class Structuretrait FileSystemItem { fn print(&self, indent: usize);}struct File { name: String,}impl FileSystemItem for File { fn print(&self, indent: usize) { println!("{:indent$}{}", "", self.name, indent = indent); }}struct Folder { name: String, children: Vec<Box<dyn FileSystemItem>>,}impl FileSystemItem for Folder { fn print(&self, indent: usize) { println!("{:indent$}{}/", "", self.name, indent = indent); for child in &self.children { child.print(indent + 2); } }}Real-World Software Example: Organization Hierarchy
Section titled “Real-World Software Example: Organization Hierarchy”Now let’s see a realistic software example - an organization system that needs to calculate total salary for employees and departments.
The Problem
Section titled “The Problem”You’re building an HR system that needs to calculate total salary. Employees can be individual workers or managers (who have subordinates). Without Composite Pattern:
# ❌ Without Composite Pattern - Different handling!
class Employee: """Individual employee""" def __init__(self, name: str, salary: float): self.name = name self.salary = salary
def get_salary(self) -> float: return self.salary
class Manager: """Manager with subordinates""" def __init__(self, name: str, salary: float): self.name = name self.salary = salary self.subordinates = [] # Only employees, not managers! self.managers = [] # Separate list for managers
def add_employee(self, employee: Employee): self.subordinates.append(employee)
def add_manager(self, manager: 'Manager'): self.managers.append(manager)
def get_salary(self) -> float: total = self.salary # Handle employees for emp in self.subordinates: total += emp.get_salary() # Handle managers separately for mgr in self.managers: total += mgr.get_salary() # Recursive return total
# Problem: Client needs to handle employees and managers differently!def calculate_total_salary(org) -> float: if isinstance(org, Employee): return org.get_salary() elif isinstance(org, Manager): total = org.salary for emp in org.subordinates: total += calculate_total_salary(emp) for mgr in org.managers: total += calculate_total_salary(mgr) return total
# Problems:# - Different interfaces for Employee and Manager# - Type checking required# - Can't treat uniformly// ❌ Without Composite Pattern - Different handling!
public class Employee { // Individual employee private String name; private double salary;
public Employee(String name, double salary) { this.name = name; this.salary = salary; }
public double getSalary() { return salary; }}
public class Manager { // Manager with subordinates private String name; private double salary; private List<Employee> subordinates; // Only employees, not managers! private List<Manager> managers; // Separate list for managers
public Manager(String name, double salary) { this.name = name; this.salary = salary; this.subordinates = new ArrayList<>(); this.managers = new ArrayList<>(); }
public void addEmployee(Employee employee) { subordinates.add(employee); }
public void addManager(Manager manager) { managers.add(manager); }
public double getSalary() { double total = salary; // Handle employees for (Employee emp : subordinates) { total += emp.getSalary(); } // Handle managers separately for (Manager mgr : managers) { total += mgr.getSalary(); // Recursive } return total; }}
// Problem: Client needs to handle employees and managers differently!public static double calculateTotalSalary(Object org) { if (org instanceof Employee) { return ((Employee) org).getSalary(); } else if (org instanceof Manager) { Manager mgr = (Manager) org; double total = mgr.salary; for (Employee emp : mgr.subordinates) { total += calculateTotalSalary(emp); } for (Manager m : mgr.managers) { total += calculateTotalSalary(m); } return total; } return 0;}
// Problems:// - Different interfaces for Employee and Manager// - Type checking required// - Can't treat uniformly// ❌ Without Composite Pattern - Different handling!
class Employee { /** Individual employee */ constructor(public name: string, public salary: number) {}
getSalary(): number { return this.salary; }}
class Manager { /** Manager with subordinates */ private subordinates: Employee[] = []; // Only employees, not managers! private managers: Manager[] = []; // Separate list for managers
constructor(public name: string, public salary: number) {}
addEmployee(employee: Employee): void { this.subordinates.push(employee); }
addManager(manager: Manager): void { this.managers.push(manager); }
getSalary(): number { let total = this.salary; // Handle employees for (const emp of this.subordinates) { total += emp.getSalary(); } // Handle managers separately for (const mgr of this.managers) { total += mgr.getSalary(); // Recursive } return total; }
getSubordinates(): Employee[] { return this.subordinates; }
getManagers(): Manager[] { return this.managers; }}
// Problem: Client needs to handle employees and managers differently!function calculateTotalSalary(org: Employee | Manager): number { if (org instanceof Employee) { return org.getSalary(); } else if (org instanceof Manager) { let total = org.salary; for (const emp of org.getSubordinates()) { total += calculateTotalSalary(emp); } for (const mgr of org.getManagers()) { total += calculateTotalSalary(mgr); } return total; } return 0;}
// Problems:// - Different interfaces for Employee and Manager// - Type checking required// - Can't treat uniformly// ❌ Without Composite Pattern - Different handling!
#include <iostream>#include <string>#include <vector>
class Employee {private: std::string name; double salary;
public: Employee(const std::string& name, double salary) : name(name), salary(salary) {}
double getSalary() const { return salary; }};
class Manager {private: std::string name; double salary; std::vector<Employee> subordinates; // Only employees, not managers! std::vector<Manager> managers; // Separate list for managers
public: Manager(const std::string& name, double salary) : name(name), salary(salary) {}
void addEmployee(const Employee& employee) { subordinates.push_back(employee); }
void addManager(const Manager& manager) { managers.push_back(manager); }
double getSalary() const { double total = salary; // Handle employees for (const auto& emp : subordinates) { total += emp.getSalary(); } // Handle managers separately for (const auto& mgr : managers) { total += mgr.getSalary(); // Recursive } return total; }
const std::vector<Employee>& getSubordinates() const { return subordinates; }
const std::vector<Manager>& getManagers() const { return managers; }};
// Problem: Client needs to handle employees and managers differently!// (This would require variant or multiple function overloads in C++)// Problems:// - Different interfaces for Employee and Manager// - Type checking required// - Can't treat uniformly// ❌ Without Composite Pattern - Different handling!
using System;using System.Collections.Generic;
public class Employee{ /** Individual employee */ public string Name { get; private set; } public double Salary { get; private set; }
public Employee(string name, double salary) { Name = name; Salary = salary; }
public double GetSalary() { return Salary; }}
public class Manager{ /** Manager with subordinates */ public string Name { get; private set; } public double Salary { get; private set; } private List<Employee> subordinates = new List<Employee>(); // Only employees, not managers! private List<Manager> managers = new List<Manager>(); // Separate list for managers
public Manager(string name, double salary) { Name = name; Salary = salary; }
public void AddEmployee(Employee employee) { subordinates.Add(employee); }
public void AddManager(Manager manager) { managers.Add(manager); }
public double GetSalary() { double total = Salary; // Handle employees foreach (var emp in subordinates) { total += emp.GetSalary(); } // Handle managers separately foreach (var mgr in managers) { total += mgr.GetSalary(); // Recursive } return total; }
public List<Employee> GetSubordinates() { return subordinates; }
public List<Manager> GetManagers() { return managers; }}
// Problem: Client needs to handle employees and managers differently!public static class OrganizationHelper{ public static double CalculateTotalSalary(object org) { if (org is Employee emp) { return emp.GetSalary(); } else if (org is Manager mgr) { double total = mgr.Salary; foreach (var employee in mgr.GetSubordinates()) { total += CalculateTotalSalary(employee); } foreach (var manager in mgr.GetManagers()) { total += CalculateTotalSalary(manager); } return total; } return 0; }}
// Problems:// - Different interfaces for Employee and Manager// - Type checking required// - Can't treat uniformly// ❌ Without Composite Pattern - Different handling!
package main
type BadEmployee struct { Name string Salary float64}
func (e *BadEmployee) GetSalary() float64 { return e.Salary }
type BadManager struct { Name string Salary float64 subordinates []*BadEmployee managers []*BadManager // Separate slices!}
func (m *BadManager) AddEmployee(e *BadEmployee) { m.subordinates = append(m.subordinates, e) }func (m *BadManager) AddManager(mgr *BadManager) { m.managers = append(m.managers, mgr) }
func (m *BadManager) GetSalary() float64 { total := m.Salary for _, e := range m.subordinates { total += e.GetSalary() } for _, mgr := range m.managers { total += mgr.GetSalary() } return total}
// Problems:// - Different interfaces for Employee and Manager// - Type checking required// - Can't treat uniformly// The Problemstruct File { name: String,}struct Folder { files: Vec<File>,}impl Folder { fn print_files(&self) { for file in &self.files { println!("{}", file.name); } }}Problems:
- Different interfaces - Employees and managers handled differently
- Type checking - Need to check if object is employee or manager
- Can’t treat uniformly - No common interface
The Solution: Composite Pattern
Section titled “The Solution: Composite Pattern”from abc import ABC, abstractmethodfrom typing import List
# Step 1: Define the component interfaceclass OrganizationComponent(ABC): """Component interface - common interface for employees and departments"""
@abstractmethod def get_salary(self) -> float: """Get the salary of the component""" pass
@abstractmethod def get_name(self) -> str: """Get the name of the component""" pass
# Step 2: Implement the leaf (Employee)class Employee(OrganizationComponent): """Employee - leaf node"""
def __init__(self, name: str, salary: float): self.name = name self.salary = salary
def get_salary(self) -> float: """Get employee salary""" return self.salary
def get_name(self) -> str: """Get employee name""" return self.name
# Step 3: Implement the composite (Department)class Department(OrganizationComponent): """Department - composite node (can contain employees and sub-departments)"""
def __init__(self, name: str): self.name = name self.members: List[OrganizationComponent] = [] # Can contain employees or departments!
def add(self, component: OrganizationComponent) -> None: """Add a component (employee or department)""" self.members.append(component)
def remove(self, component: OrganizationComponent) -> None: """Remove a component""" if component in self.members: self.members.remove(component)
def get_salary(self) -> float: """Get total salary - recursively sums members""" total = 0.0 for member in self.members: total += member.get_salary() # Works for both employees and departments! return total
def get_name(self) -> str: """Get department name""" return self.name
# Usage - Clean and uniform!def main(): # Create employees (leaves) emp1 = Employee("Alice", 50000.0) emp2 = Employee("Bob", 60000.0) emp3 = Employee("Charlie", 55000.0) emp4 = Employee("Diana", 70000.0)
# Create departments (composites) engineering = Department("Engineering") sales = Department("Sales") company = Department("Company")
# Build the hierarchy engineering.add(emp1) engineering.add(emp2) sales.add(emp3) sales.add(emp4) company.add(engineering) company.add(sales)
# Client treats employees and departments uniformly! print(f"Employee salary: ${emp1.get_salary():,.2f}") print(f"Engineering department salary: ${engineering.get_salary():,.2f}") print(f"Sales department salary: ${sales.get_salary():,.2f}") print(f"Company total salary: ${company.get_salary():,.2f}")
print("\n✅ Composite Pattern allows uniform treatment of employees and departments!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the component interfaceinterface OrganizationComponent { // Component interface - common interface for employees and departments double getSalary(); String getName();}
// Step 2: Implement the leaf (Employee)class Employee implements OrganizationComponent { // Employee - leaf node private String name; private double salary;
public Employee(String name, double salary) { this.name = name; this.salary = salary; }
@Override public double getSalary() { // Get employee salary return salary; }
@Override public String getName() { // Get employee name return name; }}
// Step 3: Implement the composite (Department)class Department implements OrganizationComponent { // Department - composite node (can contain employees and sub-departments) private String name; private List<OrganizationComponent> members; // Can contain employees or departments!
public Department(String name) { this.name = name; this.members = new ArrayList<>(); }
public void add(OrganizationComponent component) { // Add a component (employee or department) members.add(component); }
public void remove(OrganizationComponent component) { // Remove a component members.remove(component); }
@Override public double getSalary() { // Get total salary - recursively sums members double total = 0.0; for (OrganizationComponent member : members) { total += member.getSalary(); // Works for both employees and departments! } return total; }
@Override public String getName() { // Get department name return name; }}
// Usage - Clean and uniform!public class Main { public static void main(String[] args) { // Create employees (leaves) OrganizationComponent emp1 = new Employee("Alice", 50000.0); OrganizationComponent emp2 = new Employee("Bob", 60000.0); OrganizationComponent emp3 = new Employee("Charlie", 55000.0); OrganizationComponent emp4 = new Employee("Diana", 70000.0);
// Create departments (composites) Department engineering = new Department("Engineering"); Department sales = new Department("Sales"); Department company = new Department("Company");
// Build the hierarchy engineering.add(emp1); engineering.add(emp2); sales.add(emp3); sales.add(emp4); company.add(engineering); company.add(sales);
// Client treats employees and departments uniformly! System.out.printf("Employee salary: $%.2f%n", emp1.getSalary()); System.out.printf("Engineering department salary: $%.2f%n", engineering.getSalary()); System.out.printf("Sales department salary: $%.2f%n", sales.getSalary()); System.out.printf("Company total salary: $%.2f%n", company.getSalary());
System.out.println("\n✅ Composite Pattern allows uniform treatment of employees and departments!"); }}// Step 1: Define the component interfaceinterface OrganizationComponent { /** Component interface - common interface for employees and departments */ getSalary(): number; getName(): string;}
// Step 2: Implement the leaf (Employee)class Employee implements OrganizationComponent { /** Employee - leaf node */ private name: string; private salary: number;
constructor(name: string, salary: number) { this.name = name; this.salary = salary; }
getSalary(): number { /** Get employee salary */ return this.salary; }
getName(): string { /** Get employee name */ return this.name; }}
// Step 3: Implement the composite (Department)class Department implements OrganizationComponent { /** Department - composite node (can contain employees and sub-departments) */ private name: string; private members: OrganizationComponent[] = []; // Can contain employees or departments!
constructor(name: string) { this.name = name; }
add(component: OrganizationComponent): void { /** Add a component (employee or department) */ this.members.push(component); }
remove(component: OrganizationComponent): void { /** Remove a component */ const index = this.members.indexOf(component); if (index > -1) { this.members.splice(index, 1); } }
getSalary(): number { /** Get total salary - recursively sums members */ let total = 0; for (const member of this.members) { total += member.getSalary(); // Works for both employees and departments! } return total; }
getName(): string { /** Get department name */ return this.name; }}
// Usage - Clean and uniform!function main(): void { // Create employees (leaves) const emp1: OrganizationComponent = new Employee("Alice", 50000.0); const emp2: OrganizationComponent = new Employee("Bob", 60000.0); const emp3: OrganizationComponent = new Employee("Charlie", 55000.0); const emp4: OrganizationComponent = new Employee("Diana", 70000.0);
// Create departments (composites) const engineering = new Department("Engineering"); const sales = new Department("Sales"); const company = new Department("Company");
// Build the hierarchy engineering.add(emp1); engineering.add(emp2); sales.add(emp3); sales.add(emp4); company.add(engineering); company.add(sales);
// Client treats employees and departments uniformly! console.log(`Employee salary: $${emp1.getSalary().toFixed(2)}`); console.log(`Engineering department salary: $${engineering.getSalary().toFixed(2)}`); console.log(`Sales department salary: $${sales.getSalary().toFixed(2)}`); console.log(`Company total salary: $${company.getSalary().toFixed(2)}`);
console.log("\n✅ Composite Pattern allows uniform treatment of employees and departments!");}
main();#include <iostream>#include <string>#include <vector>#include <memory>#include <iomanip>
// Step 1: Define the component interfaceclass OrganizationComponent {public: virtual ~OrganizationComponent() = default; // Component interface - common interface for employees and departments virtual double getSalary() const = 0; virtual std::string getName() const = 0;};
// Step 2: Implement the leaf (Employee)class Employee : public OrganizationComponent {private: std::string name; double salary;
public: Employee(const std::string& name, double salary) : name(name), salary(salary) {}
double getSalary() const override { // Get employee salary return salary; }
std::string getName() const override { // Get employee name return name; }};
// Step 3: Implement the composite (Department)class Department : public OrganizationComponent {private: std::string name; std::vector<std::shared_ptr<OrganizationComponent>> members; // Can contain employees or departments!
public: Department(const std::string& name) : name(name) {}
void add(std::shared_ptr<OrganizationComponent> component) { // Add a component (employee or department) members.push_back(component); }
void remove(std::shared_ptr<OrganizationComponent> component) { // Remove a component auto it = std::find(members.begin(), members.end(), component); if (it != members.end()) { members.erase(it); } }
double getSalary() const override { // Get total salary - recursively sums members double total = 0.0; for (const auto& member : members) { total += member->getSalary(); // Works for both employees and departments! } return total; }
std::string getName() const override { // Get department name return name; }};
// Usage - Clean and uniform!int main() { // Create employees (leaves) auto emp1 = std::make_shared<Employee>("Alice", 50000.0); auto emp2 = std::make_shared<Employee>("Bob", 60000.0); auto emp3 = std::make_shared<Employee>("Charlie", 55000.0); auto emp4 = std::make_shared<Employee>("Diana", 70000.0);
// Create departments (composites) auto engineering = std::make_shared<Department>("Engineering"); auto sales = std::make_shared<Department>("Sales"); auto company = std::make_shared<Department>("Company");
// Build the hierarchy engineering->add(emp1); engineering->add(emp2); sales->add(emp3); sales->add(emp4); company->add(engineering); company->add(sales);
// Client treats employees and departments uniformly! std::cout << std::fixed << std::setprecision(2); std::cout << "Employee salary: $" << emp1->getSalary() << std::endl; std::cout << "Engineering department salary: $" << engineering->getSalary() << std::endl; std::cout << "Sales department salary: $" << sales->getSalary() << std::endl; std::cout << "Company total salary: $" << company->getSalary() << std::endl;
std::cout << "\n✅ Composite Pattern allows uniform treatment of employees and departments!" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define the component interfacepublic interface IOrganizationComponent{ /** Component interface - common interface for employees and departments */ double GetSalary(); string GetName();}
// Step 2: Implement the leaf (Employee)public class Employee : IOrganizationComponent{ /** Employee - leaf node */ private string name; private double salary;
public Employee(string name, double salary) { this.name = name; this.salary = salary; }
public double GetSalary() { /** Get employee salary */ return salary; }
public string GetName() { /** Get employee name */ return name; }}
// Step 3: Implement the composite (Department)public class Department : IOrganizationComponent{ /** Department - composite node (can contain employees and sub-departments) */ private string name; private List<IOrganizationComponent> members = new List<IOrganizationComponent>(); // Can contain employees or departments!
public Department(string name) { this.name = name; }
public void Add(IOrganizationComponent component) { /** Add a component (employee or department) */ members.Add(component); }
public void Remove(IOrganizationComponent component) { /** Remove a component */ members.Remove(component); }
public double GetSalary() { /** Get total salary - recursively sums members */ double total = 0.0; foreach (var member in members) { total += member.GetSalary(); // Works for both employees and departments! } return total; }
public string GetName() { /** Get department name */ return name; }}
// Usage - Clean and uniform!class Program{ static void Main() { // Create employees (leaves) IOrganizationComponent emp1 = new Employee("Alice", 50000.0); IOrganizationComponent emp2 = new Employee("Bob", 60000.0); IOrganizationComponent emp3 = new Employee("Charlie", 55000.0); IOrganizationComponent emp4 = new Employee("Diana", 70000.0);
// Create departments (composites) Department engineering = new Department("Engineering"); Department sales = new Department("Sales"); Department company = new Department("Company");
// Build the hierarchy engineering.Add(emp1); engineering.Add(emp2); sales.Add(emp3); sales.Add(emp4); company.Add(engineering); company.Add(sales);
// Client treats employees and departments uniformly! Console.WriteLine($"Employee salary: ${emp1.GetSalary():F2}"); Console.WriteLine($"Engineering department salary: ${engineering.GetSalary():F2}"); Console.WriteLine($"Sales department salary: ${sales.GetSalary():F2}"); Console.WriteLine($"Company total salary: ${company.GetSalary():F2}");
Console.WriteLine("\n✅ Composite Pattern allows uniform treatment of employees and departments!"); }}package main
import "fmt"
// Step 1: Component interfacetype OrgComponent interface { GetSalary() float64 GetName() string}
// Step 2: Leaf (Employee)type Employee struct { name string salary float64}
func NewEmployee(name string, salary float64) *Employee { return &Employee{name: name, salary: salary} }func (e *Employee) GetSalary() float64 { return e.salary }func (e *Employee) GetName() string { return e.name }
// Step 3: Composite (Department)type Department struct { name string members []OrgComponent}
func NewDepartment(name string) *Department { return &Department{name: name} }
func (d *Department) Add(c OrgComponent) { d.members = append(d.members, c)}
func (d *Department) GetSalary() float64 { total := 0.0 for _, m := range d.members { total += m.GetSalary() } return total}
func (d *Department) GetName() string { return d.name }
func main() { emp1 := NewEmployee("Alice", 50000) emp2 := NewEmployee("Bob", 60000) emp3 := NewEmployee("Charlie", 55000) emp4 := NewEmployee("Diana", 70000)
engineering := NewDepartment("Engineering") sales := NewDepartment("Sales") company := NewDepartment("Company")
engineering.Add(emp1) engineering.Add(emp2) sales.Add(emp3) sales.Add(emp4) company.Add(engineering) company.Add(sales)
fmt.Printf("Employee salary: $%.2f\n", emp1.GetSalary()) fmt.Printf("Engineering department salary: $%.2f\n", engineering.GetSalary()) fmt.Printf("Sales department salary: $%.2f\n", sales.GetSalary()) fmt.Printf("Company total salary: $%.2f\n", company.GetSalary())
fmt.Println("\n✅ Composite Pattern allows uniform treatment of employees and departments!")}// The Solution: Composite Patterntrait FileSystemItem { fn print(&self, indent: usize);}struct File { name: String,}impl FileSystemItem for File { fn print(&self, indent: usize) { println!("{:indent$}{}", "", self.name, indent = indent); }}struct Folder { name: String, children: Vec<Box<dyn FileSystemItem>>,}impl FileSystemItem for Folder { fn print(&self, indent: usize) { println!("{:indent$}{}/", "", self.name, indent = indent); for child in &self.children { child.print(indent + 2); } }}Composite Pattern Variants
Section titled “Composite Pattern Variants”There are different ways to implement the Composite Pattern:
1. Transparent Composite (Preferred)
Section titled “1. Transparent Composite (Preferred)”All methods in component interface, leaves implement empty methods for composite operations:
# Transparent Composite - all methods in interfaceclass Component: def operation(self): pass def add(self, component): pass # Leaf returns None or raises def remove(self, component): pass # Leaf returns None or raises def get_children(self): pass # Leaf returns empty list
class Leaf(Component): def operation(self): return "Leaf operation"
def add(self, component): raise NotImplementedError("Leaf cannot have children")
def remove(self, component): raise NotImplementedError("Leaf cannot have children")
def get_children(self): return []
class Composite(Component): def __init__(self): self.children = []
def operation(self): return "Composite operation"
def add(self, component): self.children.append(component)
def remove(self, component): self.children.remove(component)
def get_children(self): return self.children// Transparent Composite - all methods in interfaceinterface Component { void operation(); void add(Component component); void remove(Component component); List<Component> getChildren();}
class Leaf implements Component { @Override public void operation() { System.out.println("Leaf operation"); }
@Override public void add(Component component) { throw new UnsupportedOperationException("Leaf cannot have children"); }
@Override public void remove(Component component) { throw new UnsupportedOperationException("Leaf cannot have children"); }
@Override public List<Component> getChildren() { return Collections.emptyList(); }}
class Composite implements Component { private List<Component> children = new ArrayList<>();
@Override public void operation() { System.out.println("Composite operation"); }
@Override public void add(Component component) { children.add(component); }
@Override public void remove(Component component) { children.remove(component); }
@Override public List<Component> getChildren() { return children; }}// Transparent Composite - all methods in interfaceinterface Component { operation(): string; add(component: Component): void; remove(component: Component): void; getChildren(): Component[];}
class Leaf implements Component { operation(): string { return "Leaf operation"; }
add(component: Component): void { throw new Error("Leaf cannot have children"); }
remove(component: Component): void { throw new Error("Leaf cannot have children"); }
getChildren(): Component[] { return []; }}
class Composite implements Component { private children: Component[] = [];
operation(): string { return "Composite operation"; }
add(component: Component): void { this.children.push(component); }
remove(component: Component): void { const index = this.children.indexOf(component); if (index > -1) { this.children.splice(index, 1); } }
getChildren(): Component[] { return this.children; }}#include <vector>#include <string>#include <stdexcept>#include <memory>
// Transparent Composite - all methods in interfaceclass Component {public: virtual ~Component() = default; virtual std::string operation() = 0; virtual void add(std::shared_ptr<Component> component) = 0; virtual void remove(std::shared_ptr<Component> component) = 0; virtual std::vector<std::shared_ptr<Component>> getChildren() = 0;};
class Leaf : public Component {public: std::string operation() override { return "Leaf operation"; }
void add(std::shared_ptr<Component> component) override { throw std::runtime_error("Leaf cannot have children"); }
void remove(std::shared_ptr<Component> component) override { throw std::runtime_error("Leaf cannot have children"); }
std::vector<std::shared_ptr<Component>> getChildren() override { return {}; }};
class Composite : public Component {private: std::vector<std::shared_ptr<Component>> children;
public: std::string operation() override { return "Composite operation"; }
void add(std::shared_ptr<Component> component) override { children.push_back(component); }
void remove(std::shared_ptr<Component> component) override { auto it = std::find(children.begin(), children.end(), component); if (it != children.end()) { children.erase(it); } }
std::vector<std::shared_ptr<Component>> getChildren() override { return children; }};using System;using System.Collections.Generic;
// Transparent Composite - all methods in interfacepublic interface IComponent{ string Operation(); void Add(IComponent component); void Remove(IComponent component); List<IComponent> GetChildren();}
public class Leaf : IComponent{ public string Operation() { return "Leaf operation"; }
public void Add(IComponent component) { throw new NotSupportedException("Leaf cannot have children"); }
public void Remove(IComponent component) { throw new NotSupportedException("Leaf cannot have children"); }
public List<IComponent> GetChildren() { return new List<IComponent>(); }}
public class Composite : IComponent{ private List<IComponent> children = new List<IComponent>();
public string Operation() { return "Composite operation"; }
public void Add(IComponent component) { children.Add(component); }
public void Remove(IComponent component) { children.Remove(component); }
public List<IComponent> GetChildren() { return children; }}package main
import "errors"
// Transparent Composite - all methods in interface// In Go, leaves return errors for unsupported child opstype Component interface { Operation() string Add(Component) error Remove(Component) error GetChildren() []Component}
type Leaf struct{}
func (l *Leaf) Operation() string { return "Leaf operation" }func (l *Leaf) Add(Component) error { return errors.New("Leaf cannot have children") }func (l *Leaf) Remove(Component) error { return errors.New("Leaf cannot have children") }func (l *Leaf) GetChildren() []Component { return nil }
type Composite struct { children []Component}
func (c *Composite) Operation() string { return "Composite operation" }
func (c *Composite) Add(component Component) error { c.children = append(c.children, component) return nil}
func (c *Composite) Remove(component Component) error { for i, child := range c.children { if child == component { c.children = append(c.children[:i], c.children[i+1:]...) return nil } } return errors.New("component not found")}
func (c *Composite) GetChildren() []Component { return c.children }// 1. Transparent Composite Preferredtrait FileSystemItem { fn print(&self, indent: usize);}struct File { name: String,}impl FileSystemItem for File { fn print(&self, indent: usize) { println!("{:indent$}{}", "", self.name, indent = indent); }}struct Folder { name: String, children: Vec<Box<dyn FileSystemItem>>,}impl FileSystemItem for Folder { fn print(&self, indent: usize) { println!("{:indent$}{}/", "", self.name, indent = indent); for child in &self.children { child.print(indent + 2); } }}Pros: Uniform interface, no type checking needed
Cons: Leaves have methods they don’t use (can raise exceptions)
2. Safe Composite
Section titled “2. Safe Composite”Only leaf operations in component interface, composite operations in composite class:
# Safe Composite - only leaf operations in interfaceclass Component: def operation(self): pass
class Leaf(Component): def operation(self): return "Leaf operation"
class Composite(Component): def __init__(self): self.children = []
def operation(self): return "Composite operation"
def add(self, component): # Only in Composite self.children.append(component)
def remove(self, component): # Only in Composite self.children.remove(component)// Safe Composite - only leaf operations in interfaceinterface Component { void operation();}
class Leaf implements Component { @Override public void operation() { System.out.println("Leaf operation"); }}
class Composite implements Component { private List<Component> children = new ArrayList<>();
@Override public void operation() { System.out.println("Composite operation"); }
public void add(Component component) { // Only in Composite children.add(component); }
public void remove(Component component) { // Only in Composite children.remove(component); }}// Safe Composite - only leaf operations in interfaceinterface Component { operation(): string;}
class Leaf implements Component { operation(): string { return "Leaf operation"; }}
class Composite implements Component { private children: Component[] = [];
operation(): string { return "Composite operation"; }
add(component: Component): void { // Only in Composite this.children.push(component); }
remove(component: Component): void { // Only in Composite const index = this.children.indexOf(component); if (index > -1) { this.children.splice(index, 1); } }}#include <vector>#include <string>#include <memory>
// Safe Composite - only leaf operations in interfaceclass Component {public: virtual ~Component() = default; virtual std::string operation() = 0;};
class Leaf : public Component {public: std::string operation() override { return "Leaf operation"; }};
class Composite : public Component {private: std::vector<std::shared_ptr<Component>> children;
public: std::string operation() override { return "Composite operation"; }
void add(std::shared_ptr<Component> component) { // Only in Composite children.push_back(component); }
void remove(std::shared_ptr<Component> component) { // Only in Composite auto it = std::find(children.begin(), children.end(), component); if (it != children.end()) { children.erase(it); } }};using System.Collections.Generic;
// Safe Composite - only leaf operations in interfacepublic interface IComponent{ string Operation();}
public class Leaf : IComponent{ public string Operation() { return "Leaf operation"; }}
public class Composite : IComponent{ private List<IComponent> children = new List<IComponent>();
public string Operation() { return "Composite operation"; }
public void Add(IComponent component) // Only in Composite { children.Add(component); }
public void Remove(IComponent component) // Only in Composite { children.Remove(component); }}package main
// Safe Composite - only leaf operations in interfacetype SafeComponent interface { Operation() string}
type SafeLeaf struct{}
func (l *SafeLeaf) Operation() string { return "Leaf operation" }
type SafeComposite struct { children []SafeComponent}
func (c *SafeComposite) Operation() string { return "Composite operation" }
func (c *SafeComposite) Add(component SafeComponent) { // Only in Composite c.children = append(c.children, component)}
func (c *SafeComposite) Remove(component SafeComponent) { // Only in Composite for i, child := range c.children { if child == component { c.children = append(c.children[:i], c.children[i+1:]...) return } }}// 2. Safe Compositetrait FileSystemItem { fn print(&self, indent: usize);}struct File { name: String,}impl FileSystemItem for File { fn print(&self, indent: usize) { println!("{:indent$}{}", "", self.name, indent = indent); }}struct Folder { name: String, children: Vec<Box<dyn FileSystemItem>>,}impl FileSystemItem for Folder { fn print(&self, indent: usize) { println!("{:indent$}{}/", "", self.name, indent = indent); for child in &self.children { child.print(indent + 2); } }}Pros: Type-safe, leaves don’t have unused methods
Cons: Need type checking to use composite operations
When to Use Composite Pattern?
Section titled “When to Use Composite Pattern?”Use Composite Pattern when:
✅ You want to represent part-whole hierarchies - Objects that contain other objects
✅ You want clients to ignore - The difference between individual objects and compositions
✅ You want to treat objects uniformly - Same interface for leaves and composites
✅ You have tree structures - Hierarchical data that needs uniform treatment
✅ You want recursive operations - Operations that work on both leaves and composites
When NOT to Use Composite Pattern?
Section titled “When NOT to Use Composite Pattern?”Don’t use Composite Pattern when:
❌ Simple flat structure - No hierarchy, just a list
❌ Different operations - Leaves and composites need very different operations
❌ Performance critical - Recursive operations can be slower
❌ Over-engineering - Don’t add complexity for simple cases
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Not Using Common Interface
Section titled “Mistake 1: Not Using Common Interface”# ❌ Bad: No common interfaceclass File: def get_size(self): return 100
class Folder: def get_size(self): return 200 def add(self, item): pass
# Client needs type checking!def get_total_size(item): if isinstance(item, File): return item.get_size() elif isinstance(item, Folder): return item.get_size() # But can't treat uniformly
# ✅ Good: Common interfaceclass Component: def get_size(self): pass
class File(Component): def get_size(self): return 100
class Folder(Component): def get_size(self): return 200
# Client treats uniformly!def get_total_size(component: Component): return component.get_size() # Works for both!// ❌ Bad: No common interfaceclass File { public int getSize() { return 100; }}
class Folder { public int getSize() { return 200; } public void add(Object item) { }}
// Client needs type checking!public static int getTotalSize(Object item) { if (item instanceof File) { return ((File) item).getSize(); } else if (item instanceof Folder) { return ((Folder) item).getSize(); // But can't treat uniformly } return 0;}
// ✅ Good: Common interfaceinterface Component { int getSize();}
class File implements Component { @Override public int getSize() { return 100; }}
class Folder implements Component { @Override public int getSize() { return 200; }}
// Client treats uniformly!public static int getTotalSize(Component component) { return component.getSize(); // Works for both!}// ❌ Bad: No common interfaceclass BadFile { getSize(): number { return 100; }}
class BadFolder { getSize(): number { return 200; } add(item: any): void { }}
// Client needs type checking!function getBadTotalSize(item: any): number { if (item instanceof BadFile) { return item.getSize(); } else if (item instanceof BadFolder) { return item.getSize(); // But can't treat uniformly } return 0;}
// ✅ Good: Common interfaceinterface Component { getSize(): number;}
class GoodFile implements Component { getSize(): number { return 100; }}
class GoodFolder implements Component { getSize(): number { return 200; }}
// Client treats uniformly!function getGoodTotalSize(component: Component): number { return component.getSize(); // Works for both!}// ❌ Bad: No common interfaceclass BadFile {public: int getSize() { return 100; }};
class BadFolder {public: int getSize() { return 200; } void add(void* item) { }};
// Client needs type checking - awkward in C++!// (Would need RTTI or variants)
// ✅ Good: Common interfaceclass Component {public: virtual ~Component() = default; virtual int getSize() = 0;};
class GoodFile : public Component {public: int getSize() override { return 100; }};
class GoodFolder : public Component {public: int getSize() override { return 200; }};
// Client treats uniformly!int getGoodTotalSize(Component* component) { return component->getSize(); // Works for both!}// ❌ Bad: No common interfacepublic class BadFile{ public int GetSize() { return 100; }}
public class BadFolder{ public int GetSize() { return 200; } public void Add(object item) { }}
// Client needs type checking!public static int GetBadTotalSize(object item){ if (item is BadFile file) { return file.GetSize(); } else if (item is BadFolder folder) { return folder.GetSize(); // But can't treat uniformly } return 0;}
// ✅ Good: Common interfacepublic interface IComponent{ int GetSize();}
public class GoodFile : IComponent{ public int GetSize() { return 100; }}
public class GoodFolder : IComponent{ public int GetSize() { return 200; }}
// Client treats uniformly!public static int GetGoodTotalSize(IComponent component){ return component.GetSize(); // Works for both!}package main
// ❌ Bad: No common interface - need type switchestype BadFileNode struct{ size int }type BadFolderNode struct{ size int }
func getBadTotalSize(item interface{}) int { switch v := item.(type) { case *BadFileNode: return v.size case *BadFolderNode: return v.size } return 0}
// ✅ Good: Common interfacetype SizedComponent interface { GetSize() int}
type GoodFileNode struct{ size int }func (f *GoodFileNode) GetSize() int { return f.size }
type GoodFolderNode struct{ size int }func (f *GoodFolderNode) GetSize() int { return f.size }
// Client treats uniformly!func getGoodTotalSize(component SizedComponent) int { return component.GetSize() // Works for both!}// Mistake 1: Not Using Common Interfacestruct File { name: String,}struct Folder { files: Vec<File>,}impl Folder { fn print_files(&self) { for file in &self.files { println!("{}", file.name); } }}Mistake 2: Composite Not Delegating to Children
Section titled “Mistake 2: Composite Not Delegating to Children”# ❌ Bad: Composite doesn't delegate to childrenclass Folder: def __init__(self): self.children = [] self.size = 0 # Bad: Storing size separately
def get_size(self): return self.size # Bad: Doesn't sum children!
# ✅ Good: Composite delegates to childrenclass Folder: def __init__(self): self.children = []
def get_size(self): total = 0 for child in self.children: total += child.get_size() # Good: Delegates to children return total// ❌ Bad: Composite doesn't delegate to childrenclass Folder implements Component { private List<Component> children = new ArrayList<>(); private int size = 0; // Bad: Storing size separately
@Override public int getSize() { return size; // Bad: Doesn't sum children! }}
// ✅ Good: Composite delegates to childrenclass Folder implements Component { private List<Component> children = new ArrayList<>();
@Override public int getSize() { int total = 0; for (Component child : children) { total += child.getSize(); // Good: Delegates to children } return total; }}// ❌ Bad: Composite doesn't delegate to childrenclass BadFolder implements Component { private children: Component[] = []; private size: number = 0; // Bad: Storing size separately
getSize(): number { return this.size; // Bad: Doesn't sum children! }}
// ✅ Good: Composite delegates to childrenclass GoodFolder implements Component { private children: Component[] = [];
getSize(): number { let total = 0; for (const child of this.children) { total += child.getSize(); // Good: Delegates to children } return total; }}#include <vector>#include <memory>
// ❌ Bad: Composite doesn't delegate to childrenclass BadFolder : public Component {private: std::vector<std::shared_ptr<Component>> children; int size = 0; // Bad: Storing size separately
public: int getSize() override { return size; // Bad: Doesn't sum children! }};
// ✅ Good: Composite delegates to childrenclass GoodFolder : public Component {private: std::vector<std::shared_ptr<Component>> children;
public: int getSize() override { int total = 0; for (const auto& child : children) { total += child->getSize(); // Good: Delegates to children } return total; }};using System.Collections.Generic;
// ❌ Bad: Composite doesn't delegate to childrenpublic class BadFolder : IComponent{ private List<IComponent> children = new List<IComponent>(); private int size = 0; // Bad: Storing size separately
public int GetSize() { return size; // Bad: Doesn't sum children! }}
// ✅ Good: Composite delegates to childrenpublic class GoodFolder : IComponent{ private List<IComponent> children = new List<IComponent>();
public int GetSize() { int total = 0; foreach (var child in children) { total += child.GetSize(); // Good: Delegates to children } return total; }}package main
type SizeComponent interface{ GetSize() int }
// ❌ Bad: Composite doesn't delegate to childrentype BadFolderDelegate struct { children []SizeComponent size int // Bad: Storing size separately}func (f *BadFolderDelegate) GetSize() int { return f.size } // Bad: Doesn't sum children!
// ✅ Good: Composite delegates to childrentype GoodFolderDelegate struct { children []SizeComponent}func (f *GoodFolderDelegate) GetSize() int { total := 0 for _, child := range f.children { total += child.GetSize() // Good: Delegates to children } return total}// Mistake 2: Composite Not Delegating to Childrenstruct File { name: String,}struct Folder { files: Vec<File>,}impl Folder { fn print_files(&self) { for file in &self.files { println!("{}", file.name); } }}Mistake 3: Circular References
Section titled “Mistake 3: Circular References”# ❌ Bad: Allowing circular referencesfolder1 = Folder("Folder1")folder2 = Folder("Folder2")folder1.add(folder2)folder2.add(folder1) # Bad: Circular reference!
# ✅ Good: Prevent circular referencesclass Folder: def add(self, component): if component == self: raise ValueError("Cannot add folder to itself") if self._would_create_cycle(component): raise ValueError("Would create circular reference") self.children.append(component)
def _would_create_cycle(self, component): # Check if adding component would create cycle if component == self: return True if isinstance(component, Folder): for child in component.children: if self._would_create_cycle(child): return True return False// ❌ Bad: Allowing circular referencesFolder folder1 = new Folder("Folder1");Folder folder2 = new Folder("Folder2");folder1.add(folder2);folder2.add(folder1); // Bad: Circular reference!
// ✅ Good: Prevent circular referencesclass Folder implements Component { public void add(Component component) { if (component == this) { throw new IllegalArgumentException("Cannot add folder to itself"); } if (wouldCreateCycle(component)) { throw new IllegalArgumentException("Would create circular reference"); } children.add(component); }
private boolean wouldCreateCycle(Component component) { // Check if adding component would create cycle if (component == this) { return true; } if (component instanceof Folder) { Folder folder = (Folder) component; for (Component child : folder.children) { if (wouldCreateCycle(child)) { return true; } } } return false; }}// ❌ Bad: Allowing circular referencesconst folder1 = new Folder("Folder1");const folder2 = new Folder("Folder2");folder1.add(folder2);folder2.add(folder1); // Bad: Circular reference!
// ✅ Good: Prevent circular referencesclass Folder implements Component { private children: Component[] = [];
add(component: Component): void { if (component === this) { throw new Error("Cannot add folder to itself"); } if (this.wouldCreateCycle(component)) { throw new Error("Would create circular reference"); } this.children.push(component); }
private wouldCreateCycle(component: Component): boolean { // Check if adding component would create cycle if (component === this) { return true; } if (component instanceof Folder) { for (const child of component.children) { if (this.wouldCreateCycle(child)) { return true; } } } return false; }
getSize(): number { return 0; } getName(): string { return ""; }}#include <memory>#include <vector>#include <stdexcept>
// ❌ Bad: Allowing circular references// Folder* folder1 = new Folder("Folder1");// Folder* folder2 = new Folder("Folder2");// folder1->add(folder2);// folder2->add(folder1); // Bad: Circular reference!
// ✅ Good: Prevent circular referencesclass Folder : public Component {private: std::vector<std::shared_ptr<Component>> children;
bool wouldCreateCycle(std::shared_ptr<Component> component) { // Check if adding component would create cycle if (component.get() == this) { return true; } // Would need dynamic_cast to check if it's a Folder and traverse // This is simplified - full implementation would recursively check return false; }
public: void add(std::shared_ptr<Component> component) { if (component.get() == this) { throw std::runtime_error("Cannot add folder to itself"); } if (wouldCreateCycle(component)) { throw std::runtime_error("Would create circular reference"); } children.push_back(component); }
int getSize() const override { return 0; } std::string getName() const override { return ""; }};using System;using System.Collections.Generic;
// ❌ Bad: Allowing circular references// Folder folder1 = new Folder("Folder1");// Folder folder2 = new Folder("Folder2");// folder1.Add(folder2);// folder2.Add(folder1); // Bad: Circular reference!
// ✅ Good: Prevent circular referencespublic class Folder : IComponent{ private List<IComponent> children = new List<IComponent>();
public void Add(IComponent component) { if (component == this) { throw new ArgumentException("Cannot add folder to itself"); } if (WouldCreateCycle(component)) { throw new ArgumentException("Would create circular reference"); } children.Add(component); }
private bool WouldCreateCycle(IComponent component) { // Check if adding component would create cycle if (component == this) { return true; } if (component is Folder folder) { foreach (var child in folder.children) { if (WouldCreateCycle(child)) { return true; } } } return false; }
public int GetSize() { return 0; } public string GetName() { return ""; }}package main
import "errors"
type TreeComponent interface { GetSize() int GetName() string}
// ✅ Good: Prevent circular referencestype SafeFolder struct { name string children []TreeComponent}
func (f *SafeFolder) Add(component TreeComponent) error { if component == f { return errors.New("cannot add folder to itself") } if f.wouldCreateCycle(component) { return errors.New("would create circular reference") } f.children = append(f.children, component) return nil}
func (f *SafeFolder) wouldCreateCycle(component TreeComponent) bool { if component == f { return true } if folder, ok := component.(*SafeFolder); ok { for _, child := range folder.children { if f.wouldCreateCycle(child) { return true } } } return false}
func (f *SafeFolder) GetSize() int { return 0 }func (f *SafeFolder) GetName() string { return f.name }// Mistake 3: Circular Referencesstruct File { name: String,}struct Folder { files: Vec<File>,}impl Folder { fn print_files(&self) { for file in &self.files { println!("{}", file.name); } }}Benefits of Composite Pattern
Section titled “Benefits of Composite Pattern”- Uniform Treatment - Clients treat leaves and composites uniformly
- No Type Checking - Client doesn’t need to check if object is leaf or composite
- Recursive Operations - Operations work naturally on tree structures
- Easy to Extend - Add new component types easily
- Tree Structures - Natural representation of hierarchies
- Simplified Client Code - Client code is simpler and cleaner
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Composite Pattern?
Section titled “What is Composite Pattern?”Composite Pattern is a structural design pattern that composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.
Why Use It?
Section titled “Why Use It?”- ✅ Uniform treatment - Treat leaves and composites the same way
- ✅ No type checking - Client doesn’t need to check types
- ✅ Recursive operations - Operations work on tree structures naturally
- ✅ Tree structures - Perfect for hierarchical data
- ✅ Simplified code - Client code is simpler
How It Works?
Section titled “How It Works?”- Define component interface - Common interface for leaves and composites
- Implement leaf - Individual object that implements interface
- Implement composite - Container that implements interface and contains components
- Build tree - Compose objects into tree structure
- Treat uniformly - Client treats all components the same way
Key Components
Section titled “Key Components”Component (interface)├── Leaf (implements Component)└── Composite (implements Component, contains Components)- Component - Common interface for leaves and composites
- Leaf - Individual object (e.g., File, Employee)
- Composite - Container object (e.g., Folder, Department)
- Client - Uses components uniformly
Simple Example
Section titled “Simple Example”class Component: def operation(self): pass
class Leaf(Component): def operation(self): return "Leaf"
class Composite(Component): def __init__(self): self.children = [] def operation(self): return "Composite" def add(self, component): self.children.append(component)interface Component { String operation(); }
class Leaf implements Component { public String operation() { return "Leaf"; }}
class Composite implements Component { private List<Component> children = new ArrayList<>(); public String operation() { return "Composite"; } public void add(Component c) { children.add(c); }}interface Component { operation(): string; }
class Leaf implements Component { operation(): string { return "Leaf"; }}
class Composite implements Component { private children: Component[] = []; operation(): string { return "Composite"; } add(c: Component): void { this.children.push(c); }}class Component {public: virtual std::string operation() = 0;};
class Leaf : public Component {public: std::string operation() override { return "Leaf"; }};
class Composite : public Component { std::vector<Component*> children;public: std::string operation() override { return "Composite"; } void add(Component* c) { children.push_back(c); }};interface IComponent { string Operation(); }
class Leaf : IComponent { public string Operation() => "Leaf";}
class Composite : IComponent { private List<IComponent> children = new(); public string Operation() => "Composite"; public void Add(IComponent c) => children.Add(c);}type Component interface { Operation() string }
type Leaf struct{}func (l *Leaf) Operation() string { return "Leaf" }
type Composite struct{ children []Component }func (c *Composite) Operation() string { return "Composite" }func (c *Composite) Add(comp Component) { c.children = append(c.children, comp) }// Simple Exampletrait FileSystemItem { fn print(&self, indent: usize);}struct File { name: String,}impl FileSystemItem for File { fn print(&self, indent: usize) { println!("{:indent$}{}", "", self.name, indent = indent); }}struct Folder { name: String, children: Vec<Box<dyn FileSystemItem>>,}impl FileSystemItem for Folder { fn print(&self, indent: usize) { println!("{:indent$}{}/", "", self.name, indent = indent); for child in &self.children { child.print(indent + 2); } }}When to Use?
Section titled “When to Use?”✅ Represent part-whole hierarchies
✅ Want uniform treatment of leaves and composites
✅ Have tree structures
✅ Need recursive operations
✅ Want to simplify client code
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple flat structure
❌ Different operations for leaves and composites
❌ Performance critical (recursive overhead)
❌ Over-engineering simple cases
Key Takeaways
Section titled “Key Takeaways”- Composite Pattern = Treats leaves and composites uniformly
- Component = Common interface
- Leaf = Individual object
- Composite = Container with children
- Benefit = Uniform treatment, recursive operations
- Use Case = Hierarchical structures (file systems, organizations)
Common Pattern Structure
Section titled “Common Pattern Structure”class Component: def operation(self): pass
class Leaf(Component): def operation(self): return "Leaf operation"
class Composite(Component): def __init__(self): self.children = [] def operation(self): for child in self.children: child.operation() def add(self, component): self.children.append(component)interface Component { void operation(); }
class Leaf implements Component { public void operation() { /* Leaf operation */ }}
class Composite implements Component { private List<Component> children = new ArrayList<>(); public void operation() { for (Component c : children) c.operation(); } public void add(Component c) { children.add(c); }}interface Component { operation(): void; }
class Leaf implements Component { operation(): void { /* Leaf operation */ }}
class Composite implements Component { private children: Component[] = []; operation(): void { this.children.forEach(c => c.operation()); } add(c: Component): void { this.children.push(c); }}class Component {public: virtual void operation() = 0;};
class Leaf : public Component {public: void operation() override { /* Leaf operation */ }};
class Composite : public Component { std::vector<Component*> children;public: void operation() override { for (auto* c : children) c->operation(); } void add(Component* c) { children.push_back(c); }};interface IComponent { void Operation(); }
class Leaf : IComponent { public void Operation() { /* Leaf operation */ }}
class Composite : IComponent { private List<IComponent> children = new(); public void Operation() { foreach (var c in children) c.Operation(); } public void Add(IComponent c) => children.Add(c);}type Component interface{ Operation() }
type Leaf struct{}func (l *Leaf) Operation() { /* Leaf operation */ }
type Composite struct{ children []Component }func (c *Composite) Operation() { for _, child := range c.children { child.Operation() }}func (c *Composite) Add(comp Component) { c.children = append(c.children, comp) }// Common Pattern Structuretrait FileSystemItem { fn print(&self, indent: usize);}struct File { name: String,}impl FileSystemItem for File { fn print(&self, indent: usize) { println!("{:indent$}{}", "", self.name, indent = indent); }}struct Folder { name: String, children: Vec<Box<dyn FileSystemItem>>,}impl FileSystemItem for Folder { fn print(&self, indent: usize) { println!("{:indent$}{}/", "", self.name, indent = indent); for child in &self.children { child.print(indent + 2); } }}Remember
Section titled “Remember”- Composite Pattern treats leaves and composites uniformly
- It uses a common interface for both
- Composite delegates operations to children
- It’s perfect for tree structures
- It’s about uniformity, not just containment!
Interview Focus: Composite Pattern
Section titled “Interview Focus: Composite Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Composite Pattern is a structural design pattern that composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly through a common interface.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of when to use it
- Indicates you can explain concepts clearly
2. When to Use Composite Pattern
Section titled “2. When to Use Composite Pattern”Must mention:
- ✅ Part-whole hierarchies - Objects that contain other objects
- ✅ Uniform treatment - Want to treat leaves and composites the same way
- ✅ Tree structures - Hierarchical data
- ✅ Recursive operations - Operations that work on both leaves and composites
Example scenario to give:
“I’d use Composite Pattern when building a file system. Files are leaves, folders are composites. Both implement a FileSystemComponent interface with get_size(). When I call get_size() on a folder, it recursively sums the sizes of its children. The client doesn’t need to know if it’s a file or folder - it just calls get_size() on any component.”
3. Transparent vs Safe Composite
Section titled “3. Transparent vs Safe Composite”Must discuss:
- Transparent Composite: All methods in interface, leaves raise exceptions for composite operations
- Safe Composite: Only leaf operations in interface, composite operations only in composite class
- Preference: Transparent Composite is preferred for uniformity
Example to give:
“I prefer Transparent Composite because it provides a uniform interface. All components have the same methods. Leaves raise exceptions for composite operations like add(), but the client doesn’t need to check types. Safe Composite requires type checking, which breaks the uniformity.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Uniform treatment - Clients treat leaves and composites the same way
- No type checking - Client doesn’t need to check types
- Recursive operations - Operations work naturally on trees
- Easy to extend - Add new component types easily
- Simplified code - Client code is simpler
Trade-offs to acknowledge:
- Complexity - Adds abstraction layer
- Performance - Recursive operations can be slower
- Over-engineering risk - Can be overkill for simple cases
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Composite Pattern and Decorator Pattern?”
A:
“Composite Pattern composes objects into tree structures to represent part-whole hierarchies. Decorator Pattern adds behavior to objects dynamically. Composite is about structure and hierarchy, Decorator is about adding functionality. Composite treats leaves and composites uniformly, Decorator wraps objects to add features.”
Q: “How do you prevent circular references in Composite Pattern?”
A:
“I check if adding a component would create a cycle. Before adding a component to a composite, I traverse up the tree to see if the component already contains the composite. If it does, I raise an exception. I also prevent a composite from adding itself as a child.”
Q: “How does Composite Pattern relate to SOLID principles?”
A:
“Composite Pattern supports the Open/Closed Principle - you can add new component types without modifying existing code. It supports Single Responsibility Principle by separating leaf and composite concerns. It supports Liskov Substitution Principle - leaves and composites can be used interchangeably through the common interface. It also supports Dependency Inversion Principle by depending on the Component abstraction.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Composite Pattern clearly in one sentence
- Explain when to use it (with examples showing uniform treatment)
- Describe Transparent vs Safe Composite
- Implement Composite Pattern from scratch
- Compare with other structural patterns (Decorator, Bridge)
- List benefits and trade-offs
- Identify common mistakes (circular references, no delegation)
- Give 2-3 real-world examples
- Connect to SOLID principles
- Discuss when NOT to use it
- Explain how to prevent circular references
Remember: Composite Pattern is about treating individual objects and compositions uniformly - perfect for tree structures and hierarchies! 🌳