Other Tutorials

Mediator Design Pattern In Java

Introduction:

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.

UML Representation:

We can represent the mediator pattern with the help of following UML diagram:

Mediator design pattern

Here, we have:

  • Mediator: an interface or an abstract class defining the contract for communication among colleagues
  • ConcreteMediator: a class that implements the mediator contract; it is aware of all colleagues and their inter-communications. Any communication among colleagues happens only through a mediator
  • Colleague: an interface or an abstract class representing components of our system
  • ConcreteColleague: classes that implement the Colleague interface and are willing to interact with one another

Implementing the Mediator Pattern:

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.

Defining Colleagues:

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);
    }
}

Testing Our Implementation:

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.

Conclusion:

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.

Be the First to comment.

Leave a Comment

Your email address will not be published. Required fields are marked *