Composite Design Pattern

Nivedita KumariNivedita Kumari
3 min read

The Composite Design Pattern is a structural pattern that allows treating individual objects and compositions of objects uniformly. It is useful when you need to represent a hierarchy of objects, such as a tree structure.

Components of the Composite Design Pattern

The Composite Design Pattern consists of the following key components:

  1. Component (Abstract Interface)

    • Defines a common interface for both individual objects (leaf) and composite objects.

    • Declares methods for accessing and managing child components.

    • Example: FileSystem interface in the file system example.

    package DesignPatterns.structural.composite.filesysteminterface;

    public interface FileSystem {
        void ls();
    }
  1. Leaf (Individual Object)

    • Represents a single object in the composition.

    • Implements the Component interface but does not contain child objects.

    • Example: File class in the file system.

    package DesignPatterns.structural.composite.filesysteminterface;

    public class File implements FileSystem {
        private String fileName;

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

        @Override
        public void ls() {
            System.out.println("File: " + fileName);
        }
    }
  1. Composite (Container for Other Components)

    • Contains multiple child components (both Leaf and other Composite objects).

    • Implements the Component interface and provides methods to add, remove, and access children.

    • Example: Directory class in the file system.

    package DesignPatterns.structural.composite.filesysteminterface;

    import java.util.ArrayList;
    import java.util.List;

    public class Directory implements FileSystem {
        private String directoryName;
        private List<FileSystem> fileSystemList;

        public Directory(String directoryName) {
            this.directoryName = directoryName;
            this.fileSystemList = new ArrayList<>();
        }

        // Add component method 
        public void addComponent(FileSystem fs) {
            fileSystemList.add(fs);
        }

        @Override
        public void ls() {
            System.out.println("Directory: " + directoryName);
            for (FileSystem fileSystem : fileSystemList) {
                fileSystem.ls();
            }
        }
    }
  1. Client

    • Uses the Component interface to interact with both Leaf and Composite objects.

    • Treats both individual objects and groups the same way.

    • Example: Client class.

    package DesignPatterns.structural.composite.filesysteminterface;

    public class Client {
        public static void main(String[] args) {
            Directory root = new Directory("DSA");

            File file1 = new File("code1.cpp");
            File file2 = new File("code2.cpp");

            root.addComponent(file1);
            root.addComponent(file2);

            Directory subDir = new Directory("DAYS");
            File file3 = new File("day1code.cpp");
            File file4 = new File("day2code.cpp");

            subDir.addComponent(file3);
            subDir.addComponent(file4);

            root.addComponent(subDir);

            // Print the entire directory structure
            root.ls();
        }
    }

Pros and Cons of the Composite Design Pattern


Pros (Advantages)

  1. Simplifies Complex Hierarchies

    • Allows handling individual (Leaf) and grouped (Composite) objects uniformly.

    • Reduces code duplication when working with tree structures.

  2. Supports Open/Closed Principle

    • You can add new components (like VideoFile, AudioFile, ImageFile) without modifying existing code.
  3. Encapsulation and Loose Coupling

    • The Client does not need to worry whether it is dealing with a Leaf or Composite object.
  4. Scalability and Flexibility

    • You can dynamically add or remove components at runtime.

    • Works well for recursive structures like file systems, organizations, and UI components.

  5. Uniform Operations on Hierarchical Data

    • Operations like traversing, rendering, or searching can be performed easily on the entire tree.

Cons (Disadvantages)

  1. Increases Complexity

    • Overhead of managing parent-child relationships, especially if you only need a simple structure.
  2. Difficult to Restrict Certain Behaviors

    • Example:

      • A File should not contain child elements.

      • But since File and Directory share the same interface (FileSystem), you may need extra validation.

  3. Potential Performance Issues

    • Deep trees with many recursive operations (ls()) may impact performance.
  4. Complicates Debugging and Maintenance

    • When working with deeply nested trees, debugging and tracing function calls can be harder.

When to Use:

  • Great for modeling tree structures (file systems, UI components, organizations).
0
Subscribe to my newsletter

Read articles from Nivedita Kumari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nivedita Kumari
Nivedita Kumari

I am wokring as SDE-1 at BetterPlace