The Mediator pattern is used to simplify the communication between objects in a system by introducing a mediator object that encapsulates how these objects interact with each other.
// Mediator Interface
interface ControlUnit {
void registerCombatVehicle(CombatVehicle combatVehicle);
void sendMessage(String message, CombatVehicle combatVehicle);
}
// Concrete Mediator
class GroundControlUnit implements ControlUnit {
private List<CombatVehicle> groundVehicles;
public GroundControlUnit() {
groundVehicles = new ArrayList<>();
}
public void registerCombatVehicle(CombatVehicle combatVehicle) {
groundVehicles.add(combatVehicle);
combatVehicle.setControlUnit(this);
}
@Override
public void sendMessage(String message, CombatVehicle combatVehicle) {
for (CombatVehicle a : groundVehicles) {
if (a != combatVehicle) {
a.receiveMessage(message);
}
}
}
}
// Colleague Interface
interface CombatVehicle {
void setControlUnit(ControlUnit controlUnit);
void sendMessage(String message);
void receiveMessage(String message);
}
// Concrete Colleague
class Tank implements CombatVehicle {
private String name;
private ControlUnit controlUnit;
public Tank(String name) {
this.name = name;
}
@Override
public void setControlUnit(ControlUnit controlUnit) {
this.controlUnit = controlUnit;
}
@Override
public void sendMessage(String message) {
controlUnit.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println(getName() + " received message: " + message);
}
public String getName() {
return this.name;
}
}
// Client code
public class Main {
public static void main(String[] args) {
ControlUnit groundControlUnit = new GroundControlUnit();
CombatVehicle tank1 = new Tank("Tank 1");
CombatVehicle tank2 = new Tank("Tank 2");
CombatVehicle tank3 = new Tank("Tank 3");
groundControlUnit.registerCombatVehicle(tank1);
groundControlUnit.registerCombatVehicle(tank2);
groundControlUnit.registerCombatVehicle(tank3);
tank1.sendMessage("Hello everyone!");
// Output:
// Tank 2 received message: Hello everyone!
// Tank 3 received message: Hello everyone!
}
}
In this example, We define the ControlUnit
interface. This interface declares the methods registerCombatVehicle()
and sendMessage()
. These methods define the communication protocol between the mediator and the vehicles. We implement the GroundControlUnit
class, which serves as the concrete mediator. It maintains a list of registered combat vehicles and implements the mediator methods. The registerCombatVehicle()
method adds a vehicle to the list, sendMessage()
send messages to all other combat vehicles within a GroundControlUnit
.
The CombatVehicle
interface represents the colleagues (Combat Vehicle) that communicate through the mediator. It declares the methods setControlUnit()
, sendMessage()
and receiveMessage()
.
We implement the Tank
class, which is a concrete colleague. It has a name and a reference to the mediator (ControlUnit
). The setControlUnit()
method sets the mediator reference, the sendMessage()
directs the mediator to deliver the message to other CombatVehicle
within a ControlUnit
on behalf of the Tank
instance.
Finally, in the Main
class, we create a GroundControlUnit
instnace and create multiple instances of Tank
. We set the mediator for each Tank
and register them with the according ControlUnit
. Then, we simulate the CombatVehicle
sending messages to other CombatVehicle
via the mediator ControlUnit
The mediator pattern allows vehicles (colleagues) to communicate through the mediator (traffic control tower) without knowing each other directly. The tower handles the coordination and communication between the vehicles, enabling a decoupled and flexible system.
Use the Mediator pattern when it’s hard to change some of the classes because they are tightly coupled to a bunch of other classes.
- The pattern lets you extract all the relationships between classes into a separate class, isolating any changes to a specific component from the rest of the components.
- Assuming that without the mediator, each
Tank
instances directly interact with each other to coordinate their actions. This would lead to tight coupling, where each combat vehicle needs to know about the other combat vehicle and their specific behaviors. Any change in one combat vehicle class may require corresponding changes in other classes, making the system less flexible and harder to maintain. - By introducing the mediator, the combat vehicle only need to know about the mediator interface and communicate with it. They don't need to be aware of other combat vehicle classes or their specific implementations. The mediator encapsulates the interaction logic and coordination between the combat vehicle, reducing the direct dependencies between them.
- This design choice provides flexibility because now the combat vehicle can focus on their own responsibilities without being tightly coupled to other classes. If there is a need to introduce new types of combat vehicle or change the coordination logic, we can modify the mediator implementation (say
GroundControlUnit
(E.g:LogisticsVehicle
can only talk to each others within aControlUnit
, but cannot send messages toTank
)) without impacting the combat vehicle classes. This separation of concerns makes it easier to extend and modify the system.
- This design choice provides flexibility because now the combat vehicle can focus on their own responsibilities without being tightly coupled to other classes. If there is a need to introduce new types of combat vehicle or change the coordination logic, we can modify the mediator implementation (say
- Assuming that without the mediator, each
Use the pattern when you can’t reuse a component in a different program because it’s too dependent on other components.
- After you apply the Mediator, individual components become unaware of the other components. They could still communicate with each other, albeit indirectly, through a mediator object. To reuse a component in a different app, you need to provide it with a new mediator class.
- Say if
LogisticsVehicle
andTank
classes have direct dependencies on each other, without the Mediator pattern, if we wanted to reuse theLogisticsVehicle
andTank
classes in a different program, we would need to carry over their dependencies and tightly coupled interactions with each other. This tight coupling makes it difficult to reuse these components independently, as they rely on the specific behavior and presence of other components. - By introducing the
ControlUnit
interface andGroundControlUnit
class as a mediator, we separate the dependencies and interactions between combat vehicles. The mediator encapsulates the communication and coordination logic, allowing each combat vehicle to interact indirectly through the mediator interface.- With this approach, the
LogisticsVehicle
andTank
classes become more self-contained and less dependent on each other. They can be reused in different programs or scenarios where the specific behavior or presence of other components may differ. The mediator acts as an intermediary, handling the communication and coordination between the vehicles, which promotes reusability of the individual components.
- With this approach, the
- Say if
Use the Mediator when you find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts.
-
Say we have some common behavior, such as notifying a ground control unit, that needs to be performed by both
LogisticsVehicle
andTank
in different situations. One approach to achieve this behavior reuse could be to create subclasses for each variation. For example, we could create subclasses likeLogisticsVehicleInCity
,LogisticsVehicleInCombat
,TankInCity
,TankInCombat
, and so on, each representing a specific context where the common behavior is needed. However, this approach can quickly lead to a proliferation of subclasses. As the number of variations and contexts increases, we end up with an explosion of subclasses, resulting in a larger codebase. -
Furthermore, adding new variations or modifying the behavior for a specific context requires creating additional subclasses, making the codebase even more complex. This tightly couples the behavior with the specific subclasses and creates a lot of dependencies, making it difficult to make changes or introduce new behavior without affecting the existing code.
-
By introducing a mediator, such as the
GroundControlUnit
, all relations between components will be contained within the mediator, it’d be easier to define entirely new ways for these components to collaborate by introducing new mediator classes, without having to change the components themselves. Since the mediator encapsulates the behavior and provides a consistent interface for the vehicles to interact with. This eliminates the need for creating numerous subclasses and promotes code reuse.