Compound Patterns – Head First Approach

Alyaa TalaatAlyaa Talaat
4 min read

The Compound Patterns combine two or more design patterns to solve complex problems. By merging multiple patterns, we leverage the best aspects of each, crafting robust, flexible, and maintainable systems. These patterns demonstrate how combining design techniques can address more sophisticated software challenges.


What are Compound Patterns?

Definition: Compound Patterns integrate multiple design patterns into one solution. These combinations address complex requirements in object-oriented design by utilizing each pattern's strengths to solve particular aspects of a problem.

For example, a common compound pattern is the Model-View-Controller (MVC), which brings together the Observer, Strategy, and Composite patterns to manage user interfaces.


Problem Scenario

Imagine building a sophisticated system for a simulation of ducks in a pond. You want to model various behaviors, such as quacking, flying, and swimming, but also need to manage interactions between ducks and groups of ducks. The problem becomes multifaceted because you require dynamic behavior while maintaining flexibility to add new features like different duck types or interactions.

Simply applying a single design pattern may not suffice for this complex problem. That's where compound patterns come into play.


Using Compound Patterns

Let's explore how compound patterns can solve this simulation problem by utilizing a combination of the patterns we've learned, as discussed in the Head First Design Patterns book.

Step 1: Observer Pattern for Duck Quacking

The Observer Pattern helps us notify objects when ducks quack. Here’s how we can apply it:

public interface QuackObservable {
    public void registerObserver(Observer observer);
    public void notifyObservers();
}

Step 2: Adapter Pattern for Geese in the Duck Simulation

You have geese in the simulation, but they don't quack. To make them compatible with the duck system, use the Adapter Pattern:

public class GooseAdapter implements Quackable {
    Goose goose;

    public GooseAdapter(Goose goose) {
        this.goose = goose;
    }

    public void quack() {
        goose.honk();
    }
}

Step 3: Composite Pattern for Grouping Ducks

To manage collections of ducks, we apply the Composite Pattern. This pattern allows treating individual ducks and groups of ducks uniformly:

public class Flock implements Quackable {
    ArrayList<Quackable> quackers = new ArrayList<>();

    public void add(Quackable quacker) {
        quackers.add(quacker);
    }

    public void quack() {
        for (Quackable quacker : quackers) {
            quacker.quack();
        }
    }
}

Step 4: Decorator Pattern for Quack Counting

The Decorator Pattern helps in adding behavior, such as keeping track of how many times ducks quack, without modifying the existing classes:

public class QuackCounter implements Quackable {
    Quackable duck;
    static int numberOfQuacks;

    public QuackCounter(Quackable duck) {
        this.duck = duck;
    }

    public void quack() {
        duck.quack();
        numberOfQuacks++;
    }

    public static int getQuacks() {
        return numberOfQuacks;
    }
}

Step 5: Factory Pattern for Creating Ducks

Lastly, the Factory Pattern allows us to create families of related ducks, so we use it to instantiate different types of ducks.

public abstract class AbstractDuckFactory {
    public abstract Quackable createMallardDuck();
    public abstract Quackable createRedheadDuck();
}

Benefits of Compound Patterns

Compound patterns offer flexibility, scalability, and modularity, combining the strengths of each individual pattern to solve more complex problems. For example, the Duck Simulator is a powerful system because it uses:

  • Observer Pattern to notify when ducks quack.

  • Adapter Pattern to incorporate non-duck elements like geese.

  • Composite Pattern to handle individual and group behavior uniformly.

  • Decorator Pattern to add functionality like counting quacks.

  • Factory Pattern to handle the creation of various duck types.


When to Use Compound Patterns

  • When complexity increases: Compound patterns shine when individual patterns aren't enough to solve a problem.

  • For modular design: Each pattern addresses a specific aspect, but when combined, they create a well-structured, scalable solution.

  • To manage different aspects: If your system has multiple behaviors or components interacting in complex ways, combining patterns helps in isolating concerns and achieving maintainability.


Bullet Points

  • Compound patterns combine multiple design patterns to solve complex problems.

  • They enhance flexibility and maintainability by addressing various facets of a problem in a modular way.

  • Popular compound patterns, like MVC, blend patterns like Observer, Strategy, and Composite.

  • The Duck Simulator example demonstrates the power of combining the Observer, Adapter, Composite, Decorator, and Factory patterns.


Conclusion

The Compound Patterns provide a powerful toolset for tackling sophisticated design challenges. By blending multiple design patterns, we can create systems that are both flexible and maintainable, ensuring that different aspects of a problem are addressed with appropriate strategies.


0
Subscribe to my newsletter

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

Written by

Alyaa Talaat
Alyaa Talaat

As a continuous learner, I’m always exploring new technologies and best practices to enhance my skills in software development. I enjoy tackling complex coding challenges, whether it's optimizing performance, implementing new features, or debugging intricate issues.