Composite Pattern
🎯 Explain Like I'm 5...
Imagine you have a big toy box! Inside it, you can put toys OR smaller boxes that also contain toys (or even MORE boxes)! 📦
😟 The Problem:
- • Some things are SINGLE items (a toy car 🚗)
- • Some things are GROUPS of items (a box with many toys 📦)
- • You want to treat BOTH the same way - count them, play with them, organize them!
💡 The Solution - The Composite Pattern!
- • Make boxes and toys work the SAME way! 🎁
- • A single toy can say: 'I'm 1 toy!'
- • A box can say: 'I have 5 things inside me!' (by asking each thing inside)
- • You don't need to know if you're holding a toy or a box - both answer the same questions! 🎉
🌟 The Key Idea:
Treat single objects and groups of objects THE SAME WAY! Like folders on your computer - a folder can contain files OR more folders, and you can copy, move, or delete them all the same way! 💻
🚀 Why Is This Pattern Useful?
- ✨Build tree structures - like folders, company hierarchies, or menus!
- ✨Treat individual objects and groups uniformly!
- ✨Add new types of components easily without breaking existing code!
📋 Pattern Purpose
The Composite pattern lets you compose objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
⚡ When to Use Composite Pattern
- ✓You want to represent part-whole hierarchies of objects
- ✓You want clients to treat all objects in the structure uniformly
- ✓You're building tree structures (files/folders, menus, organization charts)
- ✓You want to ignore the difference between compositions and individual objects
🏗️ Structure Components
Component (Interface):
Declares the interface for objects in the composition
Leaf (Individual Item):
Represents individual objects with no children
Composite (Container):
Stores child components and implements child-related operations
💻 Java Implementations
Example 1: File System (Files and Folders)
A tree structure where folders can contain files or other folders.
// Component - Common interface for both files and folderspublic interface FileSystemComponent { void display(String indent); int getSize(); String getName();}// Leaf - Individual file (cannot contain other components)public class File implements FileSystemComponent { private String name; private int size; public File(String name, int size) { this.name = name; this.size = size; } @Override public void display(String indent) { System.out.println(indent + "📄 " + name + " (" + size + " KB)"); } @Override public int getSize() { return size; } @Override public String getName() { return name; }}import java.util.ArrayList;import java.util.List;// Composite - Folder that can contain files or other folderspublic class Folder implements FileSystemComponent { private String name; private List<FileSystemComponent> children = new ArrayList<>(); public Folder(String name) { this.name = name; } // Add a child component (file or folder) public void add(FileSystemComponent component) { children.add(component); } // Remove a child component public void remove(FileSystemComponent component) { children.remove(component); } @Override public void display(String indent) { System.out.println(indent + "📁 " + name + " (" + getSize() + " KB total)"); // Recursively display all children for (FileSystemComponent child : children) { child.display(indent + " "); } } @Override public int getSize() { // Calculate total size by summing all children int totalSize = 0; for (FileSystemComponent child : children) { totalSize += child.getSize(); } return totalSize; } @Override public String getName() { return name; }}// Client code - treats files and folders uniformlypublic class FileSystemDemo { public static void main(String[] args) { // Create files File file1 = new File("resume.pdf", 150); File file2 = new File("photo.jpg", 2000); File file3 = new File("document.docx", 500); File file4 = new File("code.java", 50); File file5 = new File("readme.txt", 10); // Create folders Folder documents = new Folder("Documents"); Folder photos = new Folder("Photos"); Folder projects = new Folder("Projects"); Folder root = new Folder("My Computer"); // Build the tree structure documents.add(file1); documents.add(file3); photos.add(file2); projects.add(file4); projects.add(file5); root.add(documents); root.add(photos); root.add(projects); // Display the entire file system // We treat the root folder just like any component! root.display(""); System.out.println("\n--- Individual component ---"); // We can also display just a file file1.display(" "); System.out.println("\n--- Sub-folder ---"); // Or just a folder documents.display(""); }}/* Output:📁 My Computer (2710 KB total) 📁 Documents (650 KB total) 📄 resume.pdf (150 KB) 📄 document.docx (500 KB) 📁 Photos (2000 KB total) 📄 photo.jpg (2000 KB) 📁 Projects (60 KB total) 📄 code.java (50 KB) 📄 readme.txt (10 KB)--- Individual component --- 📄 resume.pdf (150 KB)--- Sub-folder ---📁 Documents (650 KB total) 📄 resume.pdf (150 KB) 📄 document.docx (500 KB)*/Example 2: Company Hierarchy (Employees and Departments)
An organizational structure with individual employees and departments containing employees.
// Component interfacepublic interface Employee { String getName(); double getSalary(); void showDetails(String indent);}// Leaf - Individual employeepublic class Developer implements Employee { private String name; private double salary; private String position; public Developer(String name, double salary, String position) { this.name = name; this.salary = salary; this.position = position; } @Override public String getName() { return name; } @Override public double getSalary() { return salary; } @Override public void showDetails(String indent) { System.out.println(indent + "👨💻 " + position + ": " + name + " ($" + salary + ")"); }}// Leaf - Another type of individual employeepublic class Designer implements Employee { private String name; private double salary; public Designer(String name, double salary) { this.name = name; this.salary = salary; } @Override public String getName() { return name; } @Override public double getSalary() { return salary; } @Override public void showDetails(String indent) { System.out.println(indent + "🎨 Designer: " + name + " ($" + salary + ")"); }}import java.util.ArrayList;import java.util.List;// Composite - Department that contains employees or sub-departmentspublic class Department implements Employee { private String name; private List<Employee> employees = new ArrayList<>(); public Department(String name) { this.name = name; } public void add(Employee employee) { employees.add(employee); } public void remove(Employee employee) { employees.remove(employee); } @Override public String getName() { return name; } @Override public double getSalary() { // Total budget is sum of all employee salaries double total = 0; for (Employee employee : employees) { total += employee.getSalary(); } return total; } @Override public void showDetails(String indent) { System.out.println(indent + "🏢 " + name + " (Budget: $" + getSalary() + ")"); for (Employee employee : employees) { employee.showDetails(indent + " "); } }}public class CompanyDemo { public static void main(String[] args) { // Create individual employees Developer dev1 = new Developer("Alice", 80000, "Senior Developer"); Developer dev2 = new Developer("Bob", 70000, "Junior Developer"); Developer dev3 = new Developer("Charlie", 75000, "Developer"); Designer designer1 = new Designer("Diana", 65000); Designer designer2 = new Designer("Eve", 60000); // Create departments Department techDept = new Department("Technology Department"); Department designDept = new Department("Design Department"); Department company = new Department("Tech Innovations Inc."); // Build the hierarchy techDept.add(dev1); techDept.add(dev2); techDept.add(dev3); designDept.add(designer1); designDept.add(designer2); company.add(techDept); company.add(designDept); // Display the entire company structure company.showDetails(""); System.out.println("\n--- Just the tech department ---"); techDept.showDetails(""); }}/* Output:🏢 Tech Innovations Inc. (Budget: $350000.0) 🏢 Technology Department (Budget: $225000.0) 👨💻 Senior Developer: Alice ($80000.0) 👨💻 Junior Developer: Bob ($70000.0) 👨💻 Developer: Charlie ($75000.0) 🏢 Design Department (Budget: $125000.0) 🎨 Designer: Diana ($65000.0) 🎨 Designer: Eve ($60000.0)--- Just the tech department ---🏢 Technology Department (Budget: $225000.0) 👨💻 Senior Developer: Alice ($80000.0) 👨💻 Junior Developer: Bob ($70000.0) 👨💻 Developer: Charlie ($75000.0)*/Example 3: Graphics System (Shapes and Groups)
A drawing system where you can group shapes and treat groups like individual shapes.
// Component interfacepublic interface Graphic { void draw(); void move(int x, int y);}// Leaf - Individual shapepublic class Circle implements Graphic { private int x, y, radius; public Circle(int x, int y, int radius) { this.x = x; this.y = y; this.radius = radius; } @Override public void draw() { System.out.println("Drawing Circle at (" + x + "," + y + ") with radius " + radius); } @Override public void move(int newX, int newY) { this.x = newX; this.y = newY; System.out.println("Moving Circle to (" + x + "," + y + ")"); }}// Leaf - Another individual shapepublic class Rectangle implements Graphic { private int x, y, width, height; public Rectangle(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } @Override public void draw() { System.out.println("Drawing Rectangle at (" + x + "," + y + ") with size " + width + "x" + height); } @Override public void move(int newX, int newY) { this.x = newX; this.y = newY; System.out.println("Moving Rectangle to (" + x + "," + y + ")"); }}import java.util.ArrayList;import java.util.List;// Composite - Group of graphicspublic class GraphicGroup implements Graphic { private String name; private List<Graphic> graphics = new ArrayList<>(); public GraphicGroup(String name) { this.name = name; } public void add(Graphic graphic) { graphics.add(graphic); } public void remove(Graphic graphic) { graphics.remove(graphic); } @Override public void draw() { System.out.println("Drawing Group: " + name); for (Graphic graphic : graphics) { graphic.draw(); } } @Override public void move(int x, int y) { System.out.println("Moving entire group: " + name); // Move all graphics in the group for (Graphic graphic : graphics) { graphic.move(x, y); } }}public class GraphicsDemo { public static void main(String[] args) { // Create individual shapes Circle circle1 = new Circle(10, 10, 5); Circle circle2 = new Circle(20, 20, 8); Rectangle rect1 = new Rectangle(5, 5, 10, 15); Rectangle rect2 = new Rectangle(30, 30, 20, 10); // Create groups GraphicGroup group1 = new GraphicGroup("Circles"); group1.add(circle1); group1.add(circle2); GraphicGroup group2 = new GraphicGroup("Rectangles"); group2.add(rect1); group2.add(rect2); GraphicGroup mainGroup = new GraphicGroup("All Shapes"); mainGroup.add(group1); mainGroup.add(group2); // Draw everything - uniform treatment! System.out.println("=== Drawing all shapes ==="); mainGroup.draw(); System.out.println("\n=== Moving all shapes ==="); // Move the entire group at once mainGroup.move(100, 100); System.out.println("\n=== Drawing just circles ==="); group1.draw(); }}/* Output:=== Drawing all shapes ===Drawing Group: All ShapesDrawing Group: CirclesDrawing Circle at (10,10) with radius 5Drawing Circle at (20,20) with radius 8Drawing Group: RectanglesDrawing Rectangle at (5,5) with size 10x15Drawing Rectangle at (30,30) with size 20x10=== Moving all shapes ===Moving entire group: All ShapesMoving entire group: CirclesMoving Circle to (100,100)Moving Circle to (100,100)Moving entire group: RectanglesMoving Rectangle to (100,100)Moving Rectangle to (100,100)=== Drawing just circles ===Drawing Group: CirclesDrawing Circle at (100,100) with radius 5Drawing Circle at (100,100) with radius 8*/🌍 Real-World Examples
- 📁File Systems: Folders containing files and other folders
- 🖼️GUI Components: Panels containing buttons, text fields, or other panels
- 🏢Organization Charts: Departments containing employees and sub-departments
- 📋Menu Systems: Menus containing menu items or submenus
- 🎨Graphics Editors: Groups of shapes that can be moved/scaled together
✅ Benefits
- ✅Uniform Treatment: Treat individual and composite objects the same way
- ✅Easy to Add New Components: Can easily add new types of leaves or composites
- ✅Simplified Client Code: Clients don't need to distinguish between simple and complex elements
- ✅Natural Tree Structure: Perfect for hierarchical data
⚠️ Drawbacks
- ⚠️Overly General: Can make design too general, harder to restrict components
- ⚠️Type Safety: May be difficult to restrict what types can be added to composite
- ⚠️Complexity: Adds complexity for simple hierarchies
🔑 Key Points to Remember
- 1️⃣Composite lets you treat individual objects and compositions uniformly
- 2️⃣Use it for tree structures (part-whole hierarchies)
- 3️⃣Both Leaf and Composite implement the same Component interface
- 4️⃣Composite delegates operations to its children
- 5️⃣Makes it easy to add new kinds of components
💪 Practice Scenarios
- • Build a menu system with menus, submenus, and menu items
- • Create an expression evaluator (numbers, operators, and complex expressions)
- • Design a task management system with tasks and task groups
- • Implement a component hierarchy for a GUI framework
- • Build a file compression system that can compress individual files or entire directories