The Mediator Design Pattern
The Mediator Design Pattern is designed to encapsulate the communication between set of objects in a single object in order to reduce the direct relations between them.
What problems does it solve?
The Mediator Pattern provides a centralized communication by encapsulating the communication between multiple objects. This is done in order to eliminate the direct connections between separate objects or components. Later the interaction can be changed without changing the components. To implement it, colleagues hold an instance of the mediator as a member.
Glossary:
- Colleagues - the objects that interact together
- Contract of communication - the interface or abstract implementation of the mediator
- Concrete Mediator - the concrete implementation of the mediator
Pros:
- Reduces the coupling between colleagues by reducing the connections between them
- Easy to modify the way the components interact without changing the components
- Make components reusable
- Easy to add/ remove colleagues
Cons:
- The mediator can get very complex
How to recognize it?
When you call behavioral methods, that change the state of the instances of multiple types or of the same type with a single call of the method. The action calls are usually implemented using the Command Pattern.
Mediator mediator = new MediatorCloud("my_cloud_account");
Colleague homePc = new ClientWindows("homePc", mediator);
homePc.addFile("WorkFlow_Example.txt"); // files are sync'ed between clients and clients doesn't know about each other
Colleague notebook1 = new ClientMacOs("notebook1", mediator);
notebook1.addFile("CV.pdf"); // files are sync'ed between clients and clients doesn't know about each other
Colleague phone = new ClientAndroid("mobile phone", mediator); // files are sync'ed between clients and clients doesn't know about each other
Examples from Java API
java.util.Timer (all scheduleXXX() methods)
java.util.concurrent.Executor#execute()
java.util.concurrent.ExecutorService (the invokeXXX() and submit() methods)
java.util.concurrent.ScheduledExecutorService (all scheduleXXX() methods)
java.lang.reflect.Method#invoke()
Scenarios
- Traffic Cops
- Air Control Tower
- Post Office
- Group Chat App
- To avoid tight coupling - if many objects have to interact together, you can put a mediator between them.
- When you have to create an app, that should sync files between multiple clients (like Google Drive or smth else)
- If you need to refactor some tight coupled components, that interact together
- If you need to centralize the communication between components
Example 1
We are going to create a demo of “Google Drive”- like app. You can connect to a cloud account and synchronize a folder between multiple devices.
1). Create an abstract mediator
public interface Mediator {
void connect(String account, Colleague colleague);
void disconnect(String account, Colleague colleague);
void synchronize(String account);
}
2). Create an abstract colleague
/**
* The Abstract Colleague
*/
public abstract class Colleague {
@Getter
private String label;
private Mediator mediator;
private String account;
@Getter
private Set<String> files = new TreeSet<>();
@Getter
private Date filesModifiedOn;
public Colleague(String label, Mediator mediator) {
this.label = label;
this.mediator = mediator;
}
public void connect(String account) {
this.account = account;
this.mediator.connect(account, this);
}
public void disconnect() {
this.mediator.disconnect(this.account, this);
}
public void synchronize(Set<String> updatedFiles) {
this.files.clear();
this.files.addAll(updatedFiles);
this.filesModifiedOn = new Date();
System.out.println(this.account + ": " + getLabel() + " got synchronized. Files = " + this.files.size());
}
public void addFile(String file) {
this.files.add(file);
this.filesModifiedOn = new Date();
System.out.println(this.account + ": " + getLabel() + " => File added. Files = " + this.files.size());
this.mediator.synchronize(this.account);
}
}
3). Create multiple concrete colleagues
3.1). Android Client
public class ClientAndroid extends Colleague {
public ClientAndroid(String label, Mediator mediator) {
super(label, mediator);
}
}
3.2). MacOs Client
public class ClientMacOs extends Colleague {
public ClientMacOs(String label, Mediator mediator) {
super(label, mediator);
}
}
3.3). Windows Client
public class ClientWindows extends Colleague {
public ClientWindows(String label, Mediator mediator) {
super(label, mediator);
}
}
4). Create the concrete mediator
public class MediatorCloud implements Mediator {
private Map<String, List<Colleague>> connectedDevices = new HashMap<>();
@Override
public void connect(String account, Colleague device) {
List<Colleague> devices = this.connectedDevices.get(account);
if (devices == null) {
devices = new ArrayList<>();
this.connectedDevices.put(account, devices);
}
if (!devices.contains(device)) {
devices.add(device);
System.out.println(device.getLabel() + " connected to " + account);
}
synchronize(account);
}
@Override
public void disconnect(String account, Colleague device) {
List<Colleague> devices = this.connectedDevices.get(account);
if (devices != null && devices.contains(device)) {
devices.remove(device);
System.out.println(device.getLabel()+ " disconnected from " + account);
}
}
@Override
public void synchronize(String account) {
List<Colleague> devices = this.connectedDevices.get(account);
if (devices == null || devices.size() < 2) {
return;
}
devices.sort(Comparator.comparing(Colleague::getFilesModifiedOn, Comparator.nullsFirst(Comparator.naturalOrder())));
Colleague lastUpdated = devices.get(devices.size() - 1);
for (Colleague d : devices) {
if (!d.equals(lastUpdated)) {
d.synchronize(lastUpdated.getFiles());
}
}
}
}
5). Demo class
public class _Main {
public static void main(String[] args) {
String cloudAccount = "smdev.cloud";
Mediator mediator = new MediatorCloud();
// add 2 files => connect to the cloud
Colleague homePc = new ClientWindows("homePc", mediator);
homePc.addFile("WorkFlow_Example.txt");
homePc.addFile("DraftLetter.doc");
homePc.connect(cloudAccount);
System.out.println();
// connect to cloud => got synced => add 3 more files
Colleague notebook1 = new ClientMacOs("notebook1", mediator);
notebook1.connect(cloudAccount);
notebook1.addFile("CV.pdf");
notebook1.addFile("Motivation1.doc");
notebook1.addFile("Motivation2.doc");
System.out.println();
// connect to cloud => got synced => add 1 more file
Colleague workPc = new ClientWindows("workPc", mediator);
workPc.connect(cloudAccount);
workPc.addFile("Homework.doc");
// connect to clod => got synced
Colleague phone = new ClientAndroid("mobile phone", mediator);
phone.connect(cloudAccount);
}
}
Output:
null: homePc => File added. Files = 1
null: homePc => File added. Files = 2
homePc connected to smdev.cloud
notebook1 connected to smdev.cloud
smdev.cloud: notebook1 got synchronized. Files = 2
smdev.cloud: notebook1 => File added. Files = 3
smdev.cloud: homePc got synchronized. Files = 3
smdev.cloud: notebook1 => File added. Files = 4
smdev.cloud: homePc got synchronized. Files = 4
smdev.cloud: notebook1 => File added. Files = 5
smdev.cloud: homePc got synchronized. Files = 5
workPc connected to smdev.cloud
smdev.cloud: workPc got synchronized. Files = 5
smdev.cloud: homePc got synchronized. Files = 5
smdev.cloud: workPc => File added. Files = 6
smdev.cloud: homePc got synchronized. Files = 6
smdev.cloud: notebook1 got synchronized. Files = 6
mobile phone connected to smdev.cloud
smdev.cloud: mobile phone got synchronized. Files = 6
smdev.cloud: homePc got synchronized. Files = 6
smdev.cloud: notebook1 got synchronized. Files = 6