In this tutorial, we’ll learn about a behavioral pattern that promotes loose coupling between several objects communicating with one another. The idea behind the Mediator design pattern is to have a central object that encapsulates how a set of objects interact.
In the mediator pattern, we extract all the relationships between different classes in a separate class known as a mediator. This enables us to make changes to one component without impacting the entire system.
And so, we have a more loosely-coupled system that’s easier to extend and maintain.
We can represent the mediator pattern with the help of following UML diagram:
Here, we have:
Let’s take the example of an aerospace traffic control system.
Each flight needs to know about the available runway for its landing. If we allow inter-communication among the aircraft for them to find the available runway, it will lead to chaos. Rather, it’s a good idea to have an aircraft traffic control room which keeps track of all available runways and assigns them to an aircraft.
Let’s start by defining an AircraftMediator and the AircraftTrafficControlRoom:
public interface AircraftMediator { public void registerRunway(Runway runway); public String allotRunwayTo(Aircraft aircraft); public void releaseRunwayOccupiedBy(Aircraft aircraft); } public class AicraftTrafficControlRoom implements AircraftMediator { private LinkedList<Runway> availableRunways = new LinkedList<>(); private Map<Aircraft, Runway> aircraftRunwayMap = new HashMap<>(); @Override public void registerRunway(Runway runway) { this.availableRunways.add(runway); } @Override public String allotRunwayTo(Aircraft aircraft) { Runway nextAvailbleRunway = null; if(!this.availableRunways.isEmpty()) { nextAvailbleRunway = this.availableRunways.removeFirst(); this.aircraftRunwayMap.put(aircraft, runway); } return nextAvailbleRunway == null ? null : nextAvailbleRunway.getName(); } @Override public void releaseRunwayOccupiedBy(Aircraft aircraft) { if(this.aircraftRunwayMap.containsKey(aircraft)) { Runway runway = this.aircraftRunwayMap.remove(aircraft); this.availableRunways.add(runway); } } }
The air traffic control room acts as a mediator and keeps track of all available runways. It’s responsible to allot and release runways.
Now, let’s define our Aircraft, the instances of which will be the colleagues:
public interface AircraftColleague { void startLanding(); void finishLanding(); } public class Aircraft implements AircraftColleague { private AircraftMediator mediator; private String flightName; public Aircraft(AircraftMediator mediator, String flightName) { this.mediator = mediator; this.flightName = flightName; } @Override public void startLanding() { String runway = this.mediator.allotRunwayTo(this); if(runway == null) { //informing passengers System.out.println("Due to traffic, there's a delay in landing of " + this.flightName ); } else { System.out.println("Currently landing " + this.flightName + " on " + runway); } } @Override public void finishLanding() { System.out.println(this.flightName + "landed successfully"); this.mediator.releaseRunwayOccupiedBy(this); } }
Let’s see how this works together:
// In our main method Runway runwayA = new Runway("Runway A"); AircraftMediator mediator = new AircraftTrafficControlRoom(); mediator.registerRunway(runwayA); AircraftColleague wrightFlight = new Aircraft(mediator, "Wright Flight"); AircraftColleague airbusA380 = new Aircraft(mediator, "Airbus A380"); wrightFlight.startLanding(); airbusA380.startLanding(); wrightFlight.finishLanding();
On executing the above code, we’ll have the following output:
Currently landing Wright Flight on Runway A Due to traffic, there's a delay in landing of Airbus A380 Wright Flight landed successfully
If we again attempt a landing of the airbus, we’ll be able to proceed with it as the runway is now available.
In this tutorial, we learned how to implement a mediator pattern. We’ll use a mediator pattern when we have a system in which multiple objects communicate with each other. This pattern promotes loose coupling and thereby makes the system more flexible.
A common application of this pattern is a chat or messaging system. Moreover, the Java Concurrency Executor’s execute() method makes use of this pattern.