The mediator design pattern is a behavioral pattern that allows different objects to communicate with each other by using a “mediator”. The objects invoke the mediator, and the mediator does the communication for the objects.
This is particularly useful when you are dealing with hundreds of objects, who all need to communicate with each other. Now, the objects could all communicate with each other directly, but it would be very sloppy to implement.
public class Person { private String name; public List<String> receivedMessages; public Person(String name){ this.name = name; receivedMessages = new ArrayList<String>(); } public void readMessages(){ for(int i = 0; i < receivedMessages.size(); i++){ System.out.println(receivedMessages.get(i)); } } public void sendMessage(String message, List<Person> persons){ for(int i=0; i < persons.size(); i++){ //Obviously, don't send the message to yourself! if(persons.get(i) != this){ persons.get(i) .getListOfMessages() //NAME: MESSAGE format, IRC-chat style .add(name + ": " + message); } } } public List<String> getListOfMessages(){ return receivedMessages; } }
In the above snippet, we allow only the Person objects to handle the communication. Since all communication is done through the Person object only, the Person object becomes rather bloated. It is also inappropriate for the Person object to control all the communication, especially if the person had other functionalities, such as walking, running, etc etc. The Person class simply becomes too bloated.
If someone wanted to use the system above, without the introduction of any other classes, in order to communicate to every other person, they would need some sort of global container to hold a list of every person’s name. In addition to this, the person’s list of messages is practically public, which defeats the idea of encapsulating and hiding away implementation details.
This system, in action, would look something like this :
public class Main { public static void main(String[] args){ Person henry = new Person("Henry"); Person jimmy = new Person("Jimmy"); Person kim = new Person("Kim"); Person john = new Person("John"); List<Person> persons = new ArrayList<Person>(){{ add(henry); add(jimmy); add(kim); add(john); }}; //Now, Jimmy wants to send a message to everyone. jimmy.sendMessage("Hi everyone!", persons); //And now we can read out Henry's copy List<String> messages = henry.getListOfMessages(); for(int i = 0; i < messages.size(); i++){ System.out.println(messages.get(i)); } } }
OUTPUT :
Jimmy: Hi everyone!
Using the Mediator Design Pattern
The above solution works just fine. However, it is incredibly messy, and puts far too much responsibility into the hands of the Person class, when it would be more appropriate to delegate the job to another class.
Let’s implement a mediator to do this task.
public class ChatMediator{ List<String> chatHistory = new ArrayList<String>(); public static void addMessage(String message){ chatHistory.add(message); } public static void readMessages(){ for(int i = 0; i < chatHistory.size(); i++){ System.out.println(chatHistory.get(i)); } } }
Simple, right? Let’s see the effects of the mediator design pattern on the Person class’s implementation.
public class Person { private String name; public Person(String name){ this.name = name; } public void sendMessage(String message){ ChatMediator.addMessage(name + ": " + message); } public void readMessages(){ ChatMediator.readMessages(); } }
The implementation of the Person class is refreshingly simple. Two methods with only one line of code in each. By using a mediator, we cut down the complexity of the Person class by a great amount, instead, delegating the complex implementations to another class.
In other words, the ChatMediator is the middle-man. It takes the message the Person wants to send, and handles the processing by itself. By doing this, the Person class is no longer bloated, and is incredibly easy to read and use.
And finally, putting it all into action :
public class Main { public static void main(String[] args){ ChatMediator cm = new ChatMediator(); Person henry = new Person("Henry"); Person jimmy = new Person("Jimmy"); Person kim = new Person("Kim"); Person john = new Person("John"); henry.sendMessage("Hey everyone!"); jimmy.sendMessage("Hi Henry!"); kim.sendMessage("Oh, hi guys!"); john.sendMessage("Let's go to the park later!"); henry.readMessages(); } }
OUTPUT:
Henry: Hey everyone!
Jimmy: Hi Henry!
Kim: Oh, hi guys!
John: Let’s go to the park later!
Conclusion
When you want multiple objects to communicate with each other, it is often best to use the mediator design pattern. In particular, if you are dealing with a class that contains many methods, in addition to the communication components, it is a good idea to delegate some of the responsibility to another class.
The mediator design pattern makes code simpler and readable. It abstracts away details that the class does not need to know, namely how the implementation works. As far as the Person class was concerned, it only needed to know that the sendMessage method would send a message, through the help of a mediator, and that it could read out all the messages by invoking the readMessages method. The implementation details do not matter to the Person class, so they can be safely delegated away to a mediator, leading to simple and encapsulated classes.