Table of Contents
Introduction
The Mediator is an object-oriented behavioral design pattern that reduces the complexity for objects to communicate with one another. A good real-life analogy of this pattern would be a director on a movie set, who acts as a mediator between others. Instead of actors, camera crew and light crew communicating directly with each other, the mediator functions as the central point of communication.
Another good example would be an air traffic control tower, where communication does not go between planes, but via the control tower. The control tower takes care of overview and direction, which is basically the same role the Mediator pattern has in programming.
Use Cases
- You need to reduce the complexity of communication between objects
- You need a central point of communication
- You need overview and direction for communication
Pitfalls
- Too many objects that rely on a mediator can make that complex as well
- Sometimes direct interaction is desirable
Java Example
Suppose we have a taxi company with a fleet of 4 taxis. When the taxi service is called, a decision must be made about which taxi will transfer the customer. I will use 3 classes besides Main: Taxi, Customer and Mediator.
In this example I will use two continuously running Threads, one that creates Customers and one that will try to match a Taxi with a Customer. The Mediator class will take the task of matching. When a Match is found, the Taxi will run its own threads to deliver the Customer.
The simulation works like this: A Customer want to go from a location (an integer) to another location (also an integer). Each Taxi has a location and can only assigned a job when it’s not already occupied. The Mediator will find the nearest taxi that is not occupied with a job already.
Main
Let’s start with the Main class. It is relatively simple; it only creates 4 taxis and starts two threads.
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import mediator.Customer;
import mediator.Mediator;
import mediator.Taxi;
public class Main {
private static Collection<Taxi> taxiList = Collections.synchronizedList(new ArrayList<Taxi>());
public static void main(String[] args) {
taxiList.add(new Taxi(0));
taxiList.add(new Taxi(25));
taxiList.add(new Taxi(50));
taxiList.add(new Taxi(75));
Customer.startCustomerCreator();
Mediator.startMediation(taxiList);
}
}
JavaTaxi
In the runJob() method, the Taxi object goes from its own current location to the Customers “from” location, and then to the Customers “to” location. The runJob() method is called by the Mediator class. When it is done, the thread stops and job is set to “false”, so the Taxi becomes available for the Mediator again. Excuses for the long prints and hardcoded values.
package mediator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class Taxi {
private static int nextId = 1;
private int id;
private Customer customer;
private AtomicBoolean job = new AtomicBoolean(false);
private AtomicInteger location = new AtomicInteger();
public Taxi(int location) {
this.id = nextId++;
this.location.set(location);
}
public int getId() {
return this.id;
}
private synchronized void completeJob() {
job.set(false);
this.customer = null;
}
public synchronized boolean hasJob() {
return job.get();
}
public int getLocation() {
return this.location.get();
}
public boolean setJob(Customer customer) {
if (this.customer == null && !hasJob()) {
this.customer = customer;
return true;
}
else {
throw new IllegalStateException(String.format("Taxi %1$d already has a job", this.id));
}
}
/**
* Transport a customer
*/
public void runJob(Customer customer) {
Thread taxiThread = new Thread(() -> {
System.out.println(String.format("Taxi %1$d at %2$d is driving to customer: %3$s at %4$d ", this.id, this.location.get(), customer.getName(), customer.getFromLocation()));
int i = location.get(); // location of this taxi
while (i != customer.getFromLocation()) {
if (i > customer.getFromLocation()) i--;
else i++;
sleepThread(500);
}
location = new AtomicInteger(i);
System.out.println(String.format("Taxi %1$d arrived at customer %2$s at %3$d, going to %4$d", this.id, customer.getName(), this.location.get(), customer.getToLocation()));
while (i != customer.getToLocation()) {
if (i > customer.getToLocation()) i--;
else i++;
sleepThread(500);
}
location = new AtomicInteger(i);
System.out.println(String.format("Taxi %1$d delivered customer %2$s from %3$d to %4$d", this.id, customer.getName(), customer.getFromLocation(), customer.getToLocation()));
completeJob();
});
if (!hasJob()) {
this.job.set(true);
taxiThread.start();
}
else {
System.out.println(String.format("Taxi %1$d is currently busy", this.id));
}
}
public static void sleepThread(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
JavaCustomer
The Customer class creates new Customer objects at a random interval and puts them in the customerQueue. Each created Customer has a fromLocation and toLocation, both integers. Notice that startCustomerCreator() is a thread that gets initiated in Main.
package mediator;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class Customer {
private String name;
private int fromLocation, toLocation;
private static AtomicBoolean customerCreatorActive = new AtomicBoolean(false);
private static ConcurrentLinkedQueue<Customer> customerQueue = new ConcurrentLinkedQueue<Customer>();
public int getFromLocation() {
return this.fromLocation;
}
public int getToLocation() {
return this.toLocation;
}
public String getName() {
return this.name;
}
private static Customer createCustomer() {
Customer customer = new Customer();
customer.name = generateRandomString(5);
customer.fromLocation = ((int) (Math.round(Math.random() * 100)));
customer.toLocation = ((int) (Math.round(Math.random() * 100)));
return customer;
}
/**
* Creates new customers endlessly
*/
public static void startCustomerCreator() {
Thread creationThread = new Thread(() -> {
while (true) {
final Customer customer = createCustomer();
customerQueue.add(customer);
System.out.println(String.format("New customer %1$s wants from %2$d to %3$d",customer.name, customer.fromLocation, customer.toLocation));
try {
long pace = (long) (Math.random() * 5000) + 10000;
Thread.sleep(pace);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
if (customerCreatorActive.compareAndSet(false, true)) {
creationThread.start();
}
}
public static Customer getNextCustomer() {
return customerQueue.poll();
}
public static String generateRandomString(int length) {
String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random RANDOM = new Random();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int randomIndex = RANDOM.nextInt(CHARACTERS.length());
sb.append(CHARACTERS.charAt(randomIndex));
}
return sb.toString();
}
}
JavaMediator
The Mediator loads the first Customer from the Customer queue and tries to match it with a Taxi until it succeeds. The algorithm is relatively simple; it does not account for whether an occupied Taxi could reach the next Customer sooner. The Mediator immediately selects an available Taxi that is nearest to the Customer.
package mediator;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicReference;
public class Mediator {
private static final AtomicReference<Customer> customer = new AtomicReference<Customer>();
/**
* Find nearest taxi for current customer
* @param taxiList
*/
public static void findNearestTaxi(Collection<Taxi> taxiList) {
Taxi nearestTaxi = null;
int minDiff = Integer.MAX_VALUE;
for (Taxi taxi : taxiList) {
if (!taxi.hasJob()) {
int diff = Math.abs(customer.get().getFromLocation() - taxi.getLocation());
if (diff < minDiff) {
minDiff = diff;
nearestTaxi = taxi;
}
}
}
if (nearestTaxi != null) {
System.out.println(String.format("Best Taxi (%1$d) mediated at location %2$d ", nearestTaxi.getId(), nearestTaxi.getLocation()));
if (nearestTaxi.setJob(customer.get())) {
nearestTaxi.runJob(customer.get());
customer.set(null);
}
}
else {
System.out.println("All taxis occupied.");
}
}
/**
* The mediation thread, which will match Customers with Taxi's
* @param taxiList
*/
public static void startMediation(Collection<Taxi> taxiList) {
Thread mediationThread = new Thread( () -> {
while(true) {
// if we do not have a current customer to transfer, get the next in the queue
if (customer.get() == null) {
customer.set(Customer.getNextCustomer());
}
else {
System.out.print(String.format("Mediating for customer %1$s. -> ", customer.get().getName()));
synchronized(taxiList) {
findNearestTaxi(taxiList);
}
}
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
mediationThread.start();
}
}
JavaOutput
New customer M4CC9 wants from 70 to 20
Mediating for customer M4CC9. -> Best Taxi (4) mediated at location 75
Taxi 4 at 75 is driving to customer: M4CC9 at 70
Taxi 4 arrived at customer M4CC9 at 70, going to 20
New customer SZ1WW wants from 47 to 24
Mediating for customer SZ1WW. -> Best Taxi (3) mediated at location 50
Taxi 3 at 50 is driving to customer: SZ1WW at 47
Taxi 3 arrived at customer SZ1WW at 47, going to 24
New customer HZGLQ wants from 56 to 70
Mediating for customer HZGLQ. -> Best Taxi (2) mediated at location 25
Taxi 2 at 25 is driving to customer: HZGLQ at 56
Taxi 3 delivered customer SZ1WW from 47 to 24
Taxi 4 delivered customer M4CC9 from 70 to 20
New customer QJS7V wants from 94 to 48
Mediating for customer QJS7V. -> Best Taxi (3) mediated at location 24
Taxi 3 at 24 is driving to customer: QJS7V at 94
Taxi 2 arrived at customer HZGLQ at 56, going to 70
New customer SVJJ9 wants from 40 to 75
Mediating for customer SVJJ9. -> Best Taxi (4) mediated at location 20
Taxi 4 at 20 is driving to customer: SVJJ9 at 40
Taxi 2 delivered customer HZGLQ from 56 to 70
New customer T2TAO wants from 2 to 38
Taxi 4 arrived at customer SVJJ9 at 40, going to 75
Mediating for customer T2TAO. -> Best Taxi (1) mediated at location 0
Taxi 1 at 0 is driving to customer: T2TAO at 2
Taxi 1 arrived at customer T2TAO at 2, going to 38
Taxi 3 arrived at customer QJS7V at 94, going to 48
New customer C7NUV wants from 37 to 97
Mediating for customer C7NUV. -> Best Taxi (2) mediated at location 70
Taxi 2 at 70 is driving to customer: C7NUV at 37
Taxi 4 delivered customer SVJJ9 from 40 to 75
Taxi 1 delivered customer T2TAO from 2 to 38
New customer UKCYC wants from 99 to 40
Mediating for customer UKCYC. -> Best Taxi (4) mediated at location 75
Taxi 4 at 75 is driving to customer: UKCYC at 99
Taxi 2 arrived at customer C7NUV at 37, going to 97
Taxi 3 delivered customer QJS7V from 94 to 48
New customer YAEDD wants from 50 to 93
Taxi 4 arrived at customer UKCYC at 99, going to 40
Mediating for customer YAEDD. -> Best Taxi (3) mediated at location 48
Taxi 3 at 48 is driving to customer: YAEDD at 50
Taxi 3 arrived at customer YAEDD at 50, going to 93
New customer CE7KQ wants from 1 to 11
Mediating for customer CE7KQ. -> Best Taxi (1) mediated at location 38
Taxi 1 at 38 is driving to customer: CE7KQ at 1
Taxi 2 delivered customer C7NUV from 37 to 97
New customer JTQL6 wants from 43 to 99
Class Diagram

The class diagram reflects this specific implementation. The Mediator has a list of Taxi objects and gets the first Customer from the queue of customers. The Customer class automatically creates new Customers and adds them to the queue. The Mediator matches available Taxi’s with Customers.
Conclusion
The Mediator pattern mediates communication between objects. In this example it mediates between Customers and Taxis, but you could extend this functionality with different kind of customers and vehicles which would make the role of the Mediator more important.
I am aware that this example could be improved in multiple ways. For example, I have not enforced thread safety everywhere if multiple threads were to access the taxiList. Another example is that the Mediator is polling for available taxis instead of letting a taxi object notify the Mediator for availability. Some values are hard-coded, etc.
The essence of the pattern is that objects don’t have to communicate directly with each other. In this case Taxi objects don’t directly communicate with other Taxi objects.
References
Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.
Wikipedia contributors. (2024). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern