Introduction

The Composite pattern is a structural design pattern that allows you to compose objects into tree structures to represent part-whole hierarchies. This pattern lets clients treat individual objects and compositions of objects uniformly.

Key Concepts

  • Component: An abstract class or interface that declares operations common to both simple and complex objects.
  • Leaf: A class that represents simple objects in the composition. A leaf cannot have any children.
  • Composite: A class that represents complex objects that may have children. A composite can add, remove, and access its children.

Structure

The Composite pattern can be visualized as follows:

Component
  ├── Leaf
  └── Composite
        ├── Leaf
        └── Composite
              └── Leaf

UML Diagram

Component Leaf Composite
+ operation() + operation() + operation()
+ add(Component)
+ remove(Component)
+ getChild(int)

Example

Let's consider a file system where files and directories are represented using the Composite pattern.

Component Interface

// Component
public interface FileSystemComponent {
    void showDetails();
}

Leaf Class

// Leaf
public class File implements FileSystemComponent {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void showDetails() {
        System.out.println("File: " + name);
    }
}

Composite Class

// Composite
import java.util.ArrayList;
import java.util.List;

public class Directory implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> components = new ArrayList<>();

    public Directory(String name) {
        this.name = name;
    }

    public void add(FileSystemComponent component) {
        components.add(component);
    }

    public void remove(FileSystemComponent component) {
        components.remove(component);
    }

    public FileSystemComponent getChild(int index) {
        return components.get(index);
    }

    @Override
    public void showDetails() {
        System.out.println("Directory: " + name);
        for (FileSystemComponent component : components) {
            component.showDetails();
        }
    }
}

Client Code

public class CompositePatternDemo {
    public static void main(String[] args) {
        FileSystemComponent file1 = new File("file1.txt");
        FileSystemComponent file2 = new File("file2.txt");

        Directory directory1 = new Directory("directory1");
        directory1.add(file1);

        Directory directory2 = new Directory("directory2");
        directory2.add(file2);
        directory2.add(directory1);

        directory2.showDetails();
    }
}

Explanation

  1. Component Interface: FileSystemComponent declares the showDetails method.
  2. Leaf Class: File implements the FileSystemComponent interface and provides the showDetails method.
  3. Composite Class: Directory implements the FileSystemComponent interface and maintains a list of child components. It provides methods to add, remove, and get child components.
  4. Client Code: Demonstrates the creation of a composite structure and calls the showDetails method to display the hierarchy.

Practical Exercises

Exercise 1: Implement a Composite Pattern for a Menu System

Task: Create a menu system where each menu can contain submenus and menu items.

  1. Define a MenuComponent interface with methods add, remove, getChild, and display.
  2. Implement MenuItem as a leaf class.
  3. Implement Menu as a composite class.
  4. Create a client to demonstrate the menu system.

Solution:

// Component
public interface MenuComponent {
    void display();
    default void add(MenuComponent component) {
        throw new UnsupportedOperationException();
    }
    default void remove(MenuComponent component) {
        throw new UnsupportedOperationException();
    }
    default MenuComponent getChild(int index) {
        throw new UnsupportedOperationException();
    }
}

// Leaf
public class MenuItem implements MenuComponent {
    private String name;

    public MenuItem(String name) {
        this.name = name;
    }

    @Override
    public void display() {
        System.out.println("MenuItem: " + name);
    }
}

// Composite
import java.util.ArrayList;
import java.util.List;

public class Menu implements MenuComponent {
    private String name;
    private List<MenuComponent> components = new ArrayList<>();

    public Menu(String name) {
        this.name = name;
    }

    @Override
    public void add(MenuComponent component) {
        components.add(component);
    }

    @Override
    public void remove(MenuComponent component) {
        components.remove(component);
    }

    @Override
    public MenuComponent getChild(int index) {
        return components.get(index);
    }

    @Override
    public void display() {
        System.out.println("Menu: " + name);
        for (MenuComponent component : components) {
            component.display();
        }
    }
}

// Client
public class MenuSystemDemo {
    public static void main(String[] args) {
        MenuComponent menuItem1 = new MenuItem("Item 1");
        MenuComponent menuItem2 = new MenuItem("Item 2");

        Menu mainMenu = new Menu("Main Menu");
        mainMenu.add(menuItem1);

        Menu subMenu = new Menu("Sub Menu");
        subMenu.add(menuItem2);

        mainMenu.add(subMenu);

        mainMenu.display();
    }
}

Common Mistakes and Tips

  • Mistake: Forgetting to implement the add, remove, and getChild methods in the composite class.
    • Tip: Ensure that the composite class provides implementations for managing child components.
  • Mistake: Treating leaf objects as composites.
    • Tip: Ensure that leaf objects do not support methods for managing children.

Conclusion

The Composite pattern is a powerful tool for creating hierarchical structures. It allows you to treat individual objects and compositions uniformly, simplifying client code and enhancing flexibility. By understanding and applying this pattern, you can design more scalable and maintainable systems.

In the next section, we will explore the Decorator pattern, which allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.

© Copyright 2024. All rights reserved