Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
293: The Mediator Pattern
You've probably run into this before: you start a project with three or four classes that need to talk to each other. It's simple enough. But then you add a fifth class, a sixth, and suddenly you have a "spaghetti" graph of dependencies. Every class knows about every other class. If you change a method signature in one, five other classes break. This is exactly the problem the Mediator pattern is designed to kill.
The core idea is simple: instead of objects talking to each other directly, they talk to a "Mediator" object. The mediator handles the coordination. The objects themselves stay blissfully ignorant of who else exists in the system.
The Chaos of Direct Communication
Let's imagine we're building a simple Smart Home system. We have a Light and an Alarm. In a naive implementation, if the alarm triggers, it needs to tell the light to flash. It looks like this:
class SmartLight {
public void flash() { System.out.println("Lights flashing red!"); }
}
class SmartAlarm {
private SmartLight light; // Direct dependency!
public SmartAlarm(SmartLight light) { this.light = light; }
public void trigger() {
System.out.println("ALARM TRIGGERED!");
light.flash();
}
}
This works for two classes. But what happens when we add a SmartThermostat that needs to shut off the HVAC when the alarm goes off? Or a SmartLock that needs to unlock the doors? The SmartAlarm constructor becomes a nightmare of dependencies. We're creating a tight coupling that will make testing a headache.
Introducing the Central Hub
To fix this, we need a mediator. I like to think of this as the "Air Traffic Controller" of the software world. The planes (our devices) don't talk to each other; they only talk to the tower.
First, we define a HomeMediator interface. This allows us to swap out the logic of our hub without changing the devices themselves.
interface HomeMediator {
void notify(Component component, String event);
}
abstract class Component {
protected HomeMediator mediator;
public Component(HomeMediator mediator) {
this.mediator = mediator;
}
}
Now, our devices just report "events" to the mediator. They don't care who is listening.
class SmartAlarm extends Component {
public SmartAlarm(HomeMediator mediator) { super(mediator); }
public void trigger() {
System.out.println("Alarm: I've been triggered!");
mediator.notify(this, "ALARM_ON");
}
}
class SmartLight extends Component {
public SmartLight(HomeMediator mediator) { super(mediator); }
public void flash() { System.out.println("Light: Flashing red!"); }
}
Fixing my Concrete Dependency Mistake
Here is where I usually trip up when I'm rushing a build. I initially tried to make the ControlHub (the concrete mediator) use a generic List<Component> and cast them to specific types inside the notify method. It looked like this: if (component instanceof SmartLight) ....
I realized quickly that this is a "code smell." If I have to use instanceof and casting everywhere, I've just moved the spaghetti from the devices into the mediator. I've essentially created a "God Object" that knows too much. To fix this, I'll explicitly register the components the hub needs to manage. This keeps the logic clean and type-safe.
class ControlHub implements HomeMediator {
private SmartAlarm alarm;
private SmartLight light;
public void setAlarm(SmartAlarm alarm) { this.alarm = alarm; }
public void setLight(SmartLight light) { this.light = light; }
@Override
public void notify(Component component, String event) {
if (component == alarm && event.equals("ALARM_ON")) {
System.out.println("Hub: Alarm triggered! Coordinating response...");
light.flash();
// We could easily add thermostat.shutDown() here later
}
}
}
Putting the Hub to Work
Now, look at how the setup happens. The devices don't know about each other; they only know about the hub. The hub knows about the devices, but the devices are decoupled from one another.
public class Main {
public static void main(String[] args) {
ControlHub hub = new ControlHub();
SmartAlarm alarm = new SmartAlarm(hub);
SmartLight light = new SmartLight(hub);
hub.setAlarm(alarm);
hub.setLight(light);
// The alarm triggers, the hub coordinates, the light flashes.
alarm.trigger();
}
}
If we decide that the lights should only flash if it's nighttime, we only change the logic in ControlHub. We don't touch a single line of code in the SmartAlarm or SmartLight classes. That's the power of the Mediator pattern: it encapsulates how a set of objects interact.
📋 Practical Task
Implementation: Airport Runway Control Tower
Build a small system simulating an airport runway. You need to prevent two planes from using the same runway at the same time.
- Create a
TowerMediatorinterface with arequestPermission(Plane plane, String action)method. - Implement a
ControlTowerclass that tracks whether a runway is currently "occupied". - Create a
Planeclass that takes the mediator in its constructor. - The
Planeshould have a methodrequestLanding()which calls the mediator. - The
ControlTowershould only grant permission to land if the runway is clear. If it is occupied, it should tell the plane to "circle the airport". - Implement a
requestTakeoff()method that clears the runway for other planes once the plane has successfully departed.
There are no comments for now.