Skip to main content
Create your own
Lesson illustration

Choosing Communication Patterns for Scenarios

Hello! Welcome to the final lesson in our module on communication-focused behavioral patterns.

In the previous lessons, we've explored five distinct patterns, each designed to manage how objects interact:

  • Observer: For notifying multiple objects of a state change.
  • Chain of Responsibility: For passing a request along a chain of potential handlers.
  • Mediator: For centralizing communication between a set of related objects.
  • Command: For encapsulating a request as an object.
  • Iterator: For providing a standard way to traverse a collection.

Today, we will synthesize this knowledge. Your learning goal is to compare these communication-focused behavioral patterns to select the best fit for a scenario. This is a critical skill in system design interviews, where you are often expected not just to know a pattern, but to justify why it's the right choice over other alternatives.

The Common Goal: Decoupling

All five of these patterns share a common, fundamental goal: to decouple senders from receivers. They introduce a layer of indirection to prevent objects from having explicit, hard-coded references to one another. This makes your system more flexible, maintainable, and easier to test.

However, the way they achieve this decoupling and the specific problems they solve are very different. Let's build a framework to help you distinguish between them.

A Framework for Comparison

When faced with a design problem involving object communication, you can analyze it along these dimensions to guide your choice of pattern:

  1. Communication Style: What is the relationship between senders and receivers? Is it one-to-one, one-to-many, or many-to-many?
  2. Intent: What is the primary purpose of the communication? Is it notification, request handling, traversal, or orchestration?
  3. Structure: Is the communication flow centralized through a single hub or decentralized through a chain or a network of subscriptions?

Let's place each pattern into this framework.

1. Observer Pattern

  • Communication Style: One-to-many. A single Subject notifies multiple Observers.
  • Intent: To notify dependent objects about a change in state, allowing them to react accordingly. Think of event-driven systems.
  • Structure: Decentralized. The Subject maintains its own list of Observers and broadcasts updates. Observers are independent and don't know about each other.
  • Use Case: A User object changes its email address, and multiple services like NotificationService, ProfileService, and AuditService need to be updated.

2. Mediator Pattern

  • Communication Style: Many-to-many. Multiple Colleague objects interact, but all communication is routed through a central Mediator.
  • Intent: To simplify and centralize complex interaction logic. This prevents a "spaghetti" of connections where every object knows about every other object.
  • Structure: Highly centralized. The Mediator is the single hub of communication. The colleagues are loosely coupled from each other, but the Mediator becomes tightly coupled to all of them.
  • Use Case: A GUI dialog where changing a selection in a list box enables/disables a button and updates a text field. The dialog acts as the Mediator for all the widgets.

3. Chain of Responsibility Pattern

  • Communication Style: One-to-one (sequentially). A sender issues a request that travels along a dynamic chain of Handlers until one of them processes it.
  • Intent: To give multiple objects a chance to handle a request, decoupling the sender from the ultimate receiver. The sender doesn't know or care which object handles the request.
  • Structure: Decentralized and linear. Each handler holds a reference only to the next handler in the chain.
  • Use Case: An expense approval system where a request passes from a manager, to a director, to a VP, depending on the amount.

4. Command Pattern

  • Communication Style: One-to-one (decoupled). An Invoker triggers a Command object, which in turn calls an action on a Receiver.
  • Intent: To encapsulate an entire request (the action and its parameters) into a standalone object. This allows you to queue commands, log them, and implement undo/redo functionality.
  • Structure: Decouples the invoker from the receiver. The Command object is the link between them.
  • Use Case: The "undo" functionality in a text editor. Each action (typing, deleting) is a Command object that can be stored and later "undone."

5. Iterator Pattern

  • Communication Style: One-to-one (pull-based). A Client "pulls" elements one by one from an Iterator.
  • Intent: To provide a uniform way to traverse the elements of a collection without exposing its internal structure (e.g., ArrayList, HashMap, custom tree).
  • Structure: The traversal logic is encapsulated within the Iterator object itself, separate from both the collection and the client.
  • Use Case: Using Java's for-each loop on any Collection, which works seamlessly whether it's an ArrayList or a HashSet.

Key Distinctions and Trade-offs

The most challenging part of an interview is often explaining why you chose one pattern over a similar one. The following resource offers an excellent discussion comparing several of these patterns.

Mediator Design Pattern

The 'Mediator Design Pattern' article from SourceMaking has a fantastic section that directly compares Mediator with several other behavioral patterns.

Please read the section that starts with 'Chain of Responsibility, Command, Mediator, and Observer, address how you can decouple senders and receivers...'. Focus on the trade-offs it describes and the key differences between Mediator and Observer, and Mediator and Facade.

Let's crystallize those distinctions:

  • Mediator vs. Observer: This is a classic point of confusion.

    • Observer is for broadcasting. A subject notifies observers about its state change, but it doesn't care what they do. The communication is one-way (Subject -> Observers). Think of it as a publish-subscribe mechanism.
    • Mediator is for conversation. Colleagues use the mediator to communicate with each other. The communication is often two-way. The mediator orchestrates the interactions.
  • Chain of Responsibility vs. Command:

    • CoR is about finding the right object to handle a request. The request travels until it's claimed. The key is that the sender has no idea who will handle it.
    • Command is about encapsulating an action. It's not about finding a handler, but about turning the request itself into an object that you can pass around, store, or reverse.

Visualizing the Differences

A summary table can be an invaluable tool for quick recall during an interview. The image below provides a concise overview of the purpose, concept, and use cases for many behavioral patterns.

Summary of Behavioral Design Patterns
This table provides a high-level comparison of behavioral patterns. Focus on the rows for Chain of Responsibility, Command, Iterator, Mediator, and Observer to contrast their core purpose and use cases.

Similarly, comparing the UML diagrams for these patterns reveals their fundamental structural differences.

Gang of Four Design Patterns Overview
This chart shows the UML structure for various design patterns. Compare the diagrams for the communication patterns we've discussed. Notice the centralized hub in Mediator, the linear link in Chain of Responsibility, and the Subject-Observer relationship in Observer. This visual contrast helps in remembering their distinct structures.

Test your understanding!

For each of the following scenarios, which communication pattern would be the most appropriate choice and why?

  1. Scenario A: You are designing a logging framework. Messages can have different levels (e.g., DEBUG, INFO, ERROR). You want to configure different "appenders" to handle these messages. For example, a ConsoleAppender might log INFO and above, a FileAppender might log ERROR and above, and an EmailAppender might only log critical system failures. A single log event should be passed through the system to be handled by the appropriate appenders.
  2. Scenario B: You are building a flight booking system. The main booking page has many components: a date picker for departure, a date picker for return, a dropdown for the number of passengers, and a "Search Flights" button. When the user selects a one-way trip, the return date picker should be disabled. When the number of passengers is set to zero, the search button should be disabled.
  3. Scenario C: You are designing a stock market application. Multiple UI components (a ticker tape, a portfolio view, a graph) need to display the latest price of a stock. When the stock's price changes, all these components must update instantly and automatically.
  4. Scenario D: You are building a GUI-based graphics editor. You want to implement undo/redo functionality for actions like "draw circle," "change color," and "move shape."
Show answer
  1. Scenario A: Chain of Responsibility. This is a perfect fit. Each appender can be a handler in the chain. It inspects the log message's level. If it can handle it, it does, and then it passes the message to the next handler in the chain (since multiple appenders might want to log the same message). Or, if only one handler should process it, the chain can be broken after the first successful handling. The key is that the code generating the log message doesn't know or care which appenders exist or how they are configured.

  2. Scenario B: Mediator. This is a classic use case for the Mediator pattern. You have a set of colleague objects (the widgets) whose interactions are complex. Instead of making the date pickers directly reference the buttons and dropdowns (creating a "spaghetti" of dependencies), you can have the main booking page component act as the Mediator. It listens for changes in each widget and orchestrates the state of the other widgets accordingly.

  3. Scenario C: Observer. The stock data object is the Subject. The UI components (ticker, portfolio, graph) are the Observers. When the stock price (the state of the Subject) changes, it notifies all its registered observers. The observers then pull the new data and update themselves. This decouples the data source from the various ways it can be displayed.

  4. Scenario D: Command. Each user action ("draw circle," "change color") can be encapsulated as a Command object. When an action is performed, you execute the command and push it onto a history stack. To "undo," you pop the last command from the stack and call its unexecute() method. This neatly separates the action itself from the UI elements that trigger it.

Conclusion

You have now completed your survey of the core communication-focused behavioral patterns. Mastering them isn't just about memorizing their structure, but about understanding the design problems they solve and the trade-offs they introduce.

Key Takeaways:

  • All communication patterns aim to reduce coupling, but they do so in different ways.
  • Observer is for one-to-many notifications (publish-subscribe).
  • Mediator is for centralizing complex many-to-many interactions (orchestration).
  • Chain of Responsibility is for passing a request along a line of potential handlers.
  • Command is for encapsulating an action as an object, enabling features like undo/redo and queuing.
  • Iterator is for providing a standard way to traverse a collection, hiding its internal structure.

Your choice of pattern should be driven by the specific nature of the interaction you need to model.

This lesson concludes our module on communication patterns. In the next module, Behavioral Design Patterns: Responsibility and Algorithms, we will shift our focus. We'll explore powerful patterns like Strategy, State, and Template Method, which help define families of algorithms and manage how an object's behavior can change dynamically.

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

Sign up