How to Combine Observer and Mediator Patterns for Complex Event Systems in Java
Combine Observer and Mediator patterns by routing Observable subject notifications through a Mediator hub that coordinates dispatch to concrete Observers, eliminating direct dependencies between event sources and consumers.
When building enterprise Java applications with multiple event sources and cross-cutting concerns, combining the Observer and Mediator patterns creates a robust, maintainable architecture for complex event systems. The Observer pattern handles state change notifications while the Mediator pattern centralizes coordination logic, preventing the tight coupling that typically emerges in large codebases. The iluwatar/java-design-patterns repository demonstrates this integration through its generic Observable framework and Party mediator implementation.
Core Pattern Implementations
Observer Pattern Foundation
In observer/src/main/java/com/iluwatar/observer/generic/Observable.java, the base class provides the subscription mechanism through addObserver(), removeObserver(), and notifyObservers() methods. Concrete subjects extend this class to publish state changes without knowing their observers. The corresponding Observer.java interface defines the update(S subject, O observer, A argument) contract that receivers must implement.
Mediator Pattern Coordination
The Mediator pattern implementation lives in mediator/src/main/java/com/iluwatar/mediator/Party.java and PartyImpl.java. The Party interface acts as the central coordinator, while PartyImpl maintains a registry of PartyMember colleagues. When one member calls act(Action action), the mediator routes the action to all other registered members, ensuring colleagues communicate only through the Party abstraction.
Architectural Integration Strategy
Mediator as Observer Hub
To combine Observer and Mediator patterns effectively, configure the Mediator as the central hub for Observer registration. Instead of subjects notifying observers directly, Observable subjects delegate to the Mediator, which then applies routing logic before dispatching to concrete Observers. This approach consolidates event distribution logic in a single location while maintaining the loose coupling benefits of both patterns.
Decoupling Event Sources
Observable subjects maintain only a reference to the Mediator interface rather than individual observer collections. When state changes occur, the subject invokes the mediator's coordination method, allowing the Mediator to filter, transform, or prioritize events before broadcasting to registered colleagues like Wizard or Hunter. This structure ensures that adding new observer types requires changes only to the Mediator's registration logic, not modifications to existing Observable subjects.
Implementation Walkthrough
Key Source Files
The integration relies on these specific files from the iluwatar/java-design-patterns repository:
observer/src/main/java/com/iluwatar/observer/generic/Observable.java: Base class providingnotifyObservers()for subject state changesobserver/src/main/java/com/iluwatar/observer/generic/Observer.java: Generic interface requiringupdate(S subject, A argument)implementationmediator/src/main/java/com/iluwatar/mediator/Party.java: Mediator contract withaddMember()andact()methodsmediator/src/main/java/com/iluwatar/mediator/PartyImpl.java: Concrete mediator implementing event routing logicmediator/src/main/java/com/iluwatar/mediator/Wizard.java: Example concrete observer implementingPartyMember
Wiring Observable Subjects with Mediator Coordination
The following example demonstrates combining a weather-tracking Observable with the Party Mediator to coordinate game character reactions:
// Event payload definition
public record WeatherEvent(String condition, int temperature) {}
// Observable subject extending the generic framework
public class WeatherStation
extends Observable<WeatherStation, WeatherObserver, WeatherEvent> {
public void changeWeather(String condition, int temp) {
// Business logic for state change...
notifyObservers(new WeatherEvent(condition, temp));
}
}
// Observer interface specialization
public interface WeatherObserver
extends Observer<WeatherStation, WeatherObserver, WeatherEvent> {}
// Concrete observer implementing both patterns
public class GameCharacter implements WeatherObserver, PartyMember {
private final Party mediator;
public GameCharacter(Party mediator) {
this.mediator = mediator;
}
@Override
public void update(WeatherStation subject, WeatherEvent event) {
// Translate domain event to mediator action
Action action = switch (event.condition()) {
case "rain" -> Action.ATTACK;
case "sunny" -> Action.DEFEND;
default -> Action.HEAL;
};
mediator.act(this, action); // Delegate coordination to Mediator
}
@Override
public void partyAction(Action action) {
System.out.println(getClass().getSimpleName() + " performs " + action);
}
@Override
public void joinedParty(Party party) {
System.out.println(getClass().getSimpleName() + " joins the party");
}
}
// Application wiring
public class Main {
public static void main(String[] args) {
PartyImpl party = new PartyImpl(); // Mediator hub
WeatherStation weather = new WeatherStation(); // Observable subject
GameCharacter wizard = new GameCharacter(party);
GameCharacter hunter = new GameCharacter(party);
// Register with Observer pattern
weather.addObserver(wizard);
weather.addObserver(hunter);
// Register with Mediator pattern
party.addMember(wizard);
party.addMember(hunter);
// Trigger complex event propagation
weather.changeWeather("rain", 12);
weather.changeWeather("sunny", 25);
}
}
In this implementation, WeatherStation extends Observable to notify registered WeatherObserver instances. Each GameCharacter receives the weather update through the Observer interface's update() method, translates the event into a game Action, and delegates to the Party Mediator. The PartyImpl mediator then coordinates the action among all party members except the initiator, achieving complex event propagation without direct coupling between the weather system and character logic.
Benefits of Combining Observer and Mediator Patterns
Loose Coupling: Subjects depend only on the Mediator interface, not concrete observers, while observers receive events through standardized interfaces defined in Observable.java and Party.java.
Centralized Control: The Mediator consolidates routing logic, enabling filtering, transformation, and prioritization of events before dispatch, as implemented in PartyImpl.java's action distribution logic.
Scalability: Adding new event types or observers requires changes only to the Mediator's registration logic, not modifications to existing Observable subjects or concrete observers.
Testability: Observers can be unit-tested in isolation by mocking the Mediator interface, while the Mediator's routing logic can be verified independently of concrete subject implementations.
Summary
- Combine Observer and Mediator patterns by using the Mediator as a central hub that receives Observable notifications and coordinates observer dispatch to eliminate direct dependencies.
- The
Observableclass inobserver/src/main/java/com/iluwatar/observer/generic/Observable.javaprovides the subscription mechanism, whilePartyImplinmediator/src/main/java/com/iluwatar/mediator/PartyImpl.javahandles coordination. - Concrete classes implement both
ObserverandPartyMemberinterfaces to bridge pattern boundaries, translating domain events into mediator actions. - This architecture keeps subjects decoupled from observers while maintaining centralized control over event distribution logic.
- The approach scales effectively for complex systems like game engines, UI frameworks, and IoT platforms requiring multi-layered event handling.
Frequently Asked Questions
How do Observer and Mediator patterns differ in event handling?
The Observer pattern establishes a one-to-many dependency between subjects and observers where subjects notify observers directly of state changes. The Mediator pattern introduces a central coordinator that handles all interactions between colleagues, preventing them from referring to each other explicitly. When combining both, the Mediator acts as an intermediary layer that receives Observer notifications and applies business logic before dispatching to final consumers.
Can the Mediator replace the Observer pattern entirely?
No, the Mediator and Observer patterns serve complementary purposes. The Observer pattern excels at broadcasting state changes to unknown subscribers, while the Mediator excels at coordinating complex interactions between known colleagues. In the iluwatar/java-design-patterns implementation, the Mediator relies on the Observer pattern's notification mechanism while adding coordination logic that pure Observer cannot provide, such as filtering events or coordinating multi-step workflows between PartyMember instances.
What are the performance implications of adding a Mediator layer?
Adding a Mediator introduces minimal overhead—typically a single method call and collection iteration—but provides significant architectural benefits. The PartyImpl mediator in the repository maintains a simple List<PartyMember> and iterates during act() calls, resulting in O(n) complexity for event distribution. This cost is negligible compared to the maintenance burden of direct observer-to-subject dependencies in complex systems, and the Mediator enables optimizations like event batching or asynchronous dispatch that would be difficult to implement in a pure Observer architecture.
How do I handle different event types when combining these patterns?
Define specific event records or classes (like WeatherEvent in the example) and use the Mediator to route based on event type or content. The Mediator's act() method can inspect the event payload and delegate to specialized handler methods, or you can create multiple Mediator implementations for different event categories. In the provided example, the GameCharacter class translates WeatherEvent into Action enums before calling the Mediator, demonstrating how domain-specific events can be normalized into the Mediator's coordination protocol.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →