Skip to main content
Create your own
Lesson illustration

Composite Pattern: Uniform Object and Composition Handling

Hello! Welcome to our next lesson in the Structural Design Patterns module.

In the previous lesson, we explored the Proxy pattern, which uses a surrogate object to control access to another object. It acts as an intermediary for reasons like security, performance (lazy loading), or caching.

Today, we'll examine the Composite pattern. This pattern also deals with object structure, but its goal is different. Your learning outcome for this lesson is to apply the Composite pattern to treat individual objects and compositions uniformly.

Instead of controlling access, the Composite pattern is about building tree-like structures and allowing clients to interact with individual objects (leaves) and groups of objects (composites) in the exact same way. This is a fundamental pattern for representing part-whole hierarchies, which are common in system design problems.

What is the Composite Pattern?

The core idea of the Composite pattern is to compose objects into a tree structure and then work with these structures as if they were individual objects. Think of a computer's file system:

  • You have individual files.
  • You have directories (or folders), which can contain files and other directories.

You can perform certain operations on both files and directories. For example, you can get the name of a file, and you can get the name of a directory. You can delete a file, and you can delete a directory (which recursively deletes its contents). The Composite pattern provides a way to model this relationship cleanly.

Let's start with a short video that introduces this concept using the file system and a delivery box analogy.

32. All Structural Design Patterns | Decorator, Proxy, Composite, Adapter, Bridge, Facade, FlyWeight

This video from Concept && Coding provides a quick and clear introduction to the problem that the Composite pattern solves: handling 'object inside object' tree-like structures.

Watch the segment from 00:13:30 to 00:15:38. Pay attention to how the examples of a delivery box and a file system both involve two types of objects: 'leaf' nodes (the final item/file) and 'composite' nodes (a box/directory that holds other things).

This pattern is incredibly useful in many real-world scenarios. To broaden your perspective, let's look at a few more examples.

Composite Design Pattern — Java LLD + UML + Real Use Cases

The article 'Composite Design Pattern' from dev.to offers a concise definition and a great table of real-world use cases.

Read the sections 'What Is the Composite Pattern?' and 'Real-World Use Cases'. As you read, think about how an organizational chart or a UI component hierarchy (like Swing components in Java) could be modeled as a tree of uniform objects.

The Structure of the Composite Pattern

To achieve this uniform treatment, the pattern relies on three key participants:

  1. Component: This is an interface that declares the common operations for both simple (leaf) and complex (composite) objects in the composition.
  2. Leaf: This class represents an individual object. It implements the Component interface but has no children. In the file system example, a File is a Leaf.
  3. Composite: This class represents a group of objects. It implements the Component interface and contains a collection of child objects (which can be either Leaves or other Composites). In our example, a Directory is a Composite.

The magic of the pattern lies in the Composite class. It holds a list of Component objects (List<Component>). This allows it to hold both Leaf and other Composite objects, enabling the tree structure. When an operation is called on a Composite, it typically delegates that operation to its children.

Let's visualize this with a UML diagram.

UML Class Diagram for Composite Design Pattern
This UML diagram illustrates the structure of the Composite pattern using a task management system. The `Client` interacts with the `Task` interface. Both `SimpleTask` (a Leaf) and `TaskList` (a Composite that holds other Tasks) implement this interface, allowing for uniform treatment.

Implementing the Composite Pattern in Java

Now, let's dive into a practical implementation. A great way to understand the pattern is to build something with it. We'll follow along with a detailed video that assembles a computer from its parts. This is a perfect example of a part-whole hierarchy.

The main components will be:

  • Component: A Component interface with a showPrice() method.
  • Leaf: Individual parts like HardDrive, RAM, CPU. They have a price.
  • Composite: Assemblies like Cabinet or Motherboard, which are composed of other components. Their price is the sum of their parts.

Composite Design Pattern Practical

This detailed video from Telusko walks through building a computer system using the Composite pattern from scratch. Since you have a strong Java background, you'll be able to appreciate the nuances of the implementation.

Watch the video from the beginning to 16:28. I recommend pausing and following along with the code structure in your head or even in an IDE. (00:00 - 04:03): Focus on the initial setup: defining the Component interface and the basic structure for the Leaf and Composite classes. (04:03 - 09:06): Observe how the Composite class manages a List<Component> and how the showPrice() method is implemented differently for the Leaf (shows its own price) and the Composite (iterates and calls showPrice() on its children). (09:06 - 16:28): See how the client code creates a complex tree of objects (a Computer made of a Cabinet and Peripherals, which are in turn made of other parts) and then, with a single call to computer.showPrice(), triggers the entire recursive price calculation. This demonstrates the power of uniform treatment.

For a quick reference, here is a similar implementation using the file system example in text format. This shows the same structure: a component interface (FileSystemNode), a leaf class (File), and a composite class (Directory).

Composite Design Pattern — Java LLD + UML + Real Use Cases

This article provides a very clean and straightforward Java implementation of the file system example. It's a great way to review the code structure we just saw in the video.

Read the section 'Java Example — File System', including all the code snippets for FileSystemNode.java, File.java, Directory.java, and the Client.java. Notice the recursive call in the Directory's show() method.

Test your understanding!

You are designing a system for calculating the total calories in a meal. A meal can be composed of individual food items (e.g., "Apple", 100 calories) and also "recipes" (e.g., "Fruit Salad"), which are themselves composed of other food items or even other, smaller recipes.

You need to be able to calculate the total calories for any item, whether it's a single apple or a complex multi-level meal. How would you apply the Composite pattern to model this? Identify the Component, Leaf, and Composite.

Show answer
  • Component: You would define an interface, let's call it FoodComponent, with a single method: int getCalories().

  • Leaf: You would create a class named FoodItem that implements FoodComponent. It would have a name and a calorie count. Its getCalories() method would simply return its own calorie value.

    class FoodItem implements FoodComponent {
        private String name;
        private int calories;
        // constructor...
        public int getCalories() { return this.calories; }
    }
    
  • Composite: You would create a class named Recipe that also implements FoodComponent. It would contain a List<FoodComponent> ingredients. Its getCalories() method would iterate through the list of ingredients and sum up the results of calling getCalories() on each one.

    class Recipe implements FoodComponent {
        private String name;
        private List<FoodComponent> ingredients = new ArrayList<>();
        // constructor, addIngredient()...
        public int getCalories() {
            return ingredients.stream()
                              .mapToInt(FoodComponent::getCalories)
                              .sum();
        }
    }
    

This way, the client code can call getCalories() on any FoodComponent without needing to know if it's a simple FoodItem or a complex Recipe.

Advantages and Disadvantages

Like any pattern, the Composite pattern has trade-offs. It's important to know when it's the right tool for the job.

  • Main Advantage: It simplifies the client code significantly. The client doesn't need to write if/else statements to check if an object is a leaf or a composite; it treats them all the same. It also makes it easy to add new kinds of components without changing the client (Open/Closed Principle).
  • Main Disadvantage: It can sometimes make the design too general. For example, you might have methods in the Component interface (like add or remove) that only make sense for Composites, not for Leaves. This can force you to implement empty or exception-throwing methods in the Leaf classes, which can feel unclean.

The dev.to article provides a concise summary of these points.

Composite Design Pattern — Java LLD + UML + Real Use Cases

Let's review the benefits and limitations of this pattern, which is crucial for justifying your design choices in an interview.

Read the sections 'Benefits', 'Limitations', and pay special attention to the 'Interview Tip'. Understanding when to use this pattern (for recursive traversal or group-leaf relationships) is key.

Conclusion

You have now learned how to apply the Composite pattern to build flexible, tree-like object structures.

Key Takeaways:

  • Purpose: To compose objects into part-whole hierarchies and allow clients to treat individual objects (leaves) and group objects (composites) uniformly.
  • Structure: Relies on a shared Component interface, implemented by both Leaf and Composite classes. The Composite holds a collection of Components, enabling recursion.
  • Key Benefit: Simplifies client code and promotes the Open/Closed Principle by making it easy to add new component types.
  • Interview Cue: Use this pattern when you identify a problem that involves a hierarchical, tree-like structure where operations need to be performed on both individual elements and entire sub-trees (e.g., file systems, GUI layouts, organizational charts, nested menus).

In our next and final lesson on structural patterns, we will cover the Bridge pattern. This pattern is designed to decouple an abstraction from its implementation so that the two can evolve independently. This is particularly useful for managing complexity in systems with multiple platforms or variations.

Can't find a good explanation? Sign up and we'll make it for you

Sign up