Sunday, November 30, 2014

Mediator Design Pattern

Simply put, the mediator pattern is used when complex calling relationship between classes exists and we want to avoid placing the code to invoke various classes based on various considerations in all the classes involved.

Purpose

According to the GoF, the mediator pattern
Allows loose coupling by encapsulating the way disparate sets of objects interact and communicate with each other. Allows for the actions of each object set to vary independently of one another.

When to use this pattern?

  1. The pattern is useful in any scenario where we want to centralize the complex relationships between multiple classes.
  2. This pattern is extensively used for GUI programming.

Terminology

Before we go further, it makes sense to understand the terminology.

Mediator

This refers to the class that acts as the intermediate to redirect calls from one class to another.

Colleague

These are the classes that want to communicate with each other or are the actual actors in the flow.

Example

It is easier to understand this with an example. Consider a scenario where the code needs to be go through a flow. The flow contains various colleagues called in different order based on the user's input:

User Input Sequence of call for Colleagues
1 C1 ⇒ C2 ⇒ C3
2 C2 ⇒ C4 ⇒ C3 ⇒ C6
3 C1 ⇒ C5 ⇒ C2
4 C1 ⇒ (if value < 0) { C8 ⇒ C2 } else { C6 ⇒ C3 ⇒ C7 } ⇒ C4

Pictorially

This can be represented pictorially as follows:

This would mean that the code in each of the colleagues would need to have the intelligence to call the others. This leads to a lot of duplication of code.

When a mediator is used, the code organization looks like this:

Code Snippet

The complete code is present at my github repo. The following sections show a relevant part of the code.

Class Diagram

IColleague

package com.karthicksmail.designpatterns.mediator;

/**
 * @author karthicksmail
 */
public interface IColleague {
 void execute();
}

ConcreteColleague1

package com.karthicksmail.designpatterns.mediator;

/**
 * @author karthicksmail
 *
 */
public class ConcreteColleague1 implements IColleague {
 /* (non-Javadoc)
  * @see com.karthicksmail.designpatterns.mediator.IColleague#execute()
  */
 public void execute() {
  System.out.println("Executing " + this.getClass().getSimpleName());
 }

}
You can create as many ConcreteColleague classes as required.

Mediator

package com.karthicksmail.designpatterns.mediator;

import java.util.ArrayList;

/**
 * @author karthicksmail
 *
 */
public class Mediator {
 private ArrayList colleagueList = new ArrayList();
 private boolean value = false;

 public boolean register(IColleague colleague) {
  System.out.println("Registering " + colleague.getClass().getSimpleName());
  return colleagueList.add(colleague);
 }

 public void setValue(boolean  value) {
  this.value = value;
 }

 public void invokeFlow(int flow) {
  switch (flow) {
  case 1: 
   colleagueList.get(0).execute();
   colleagueList.get(1).execute();
   colleagueList.get(2).execute();
   break;
  case 2:
   colleagueList.get(1).execute();
   colleagueList.get(3).execute();
   colleagueList.get(2).execute();
   colleagueList.get(5).execute();
   break;
  case 3:
   colleagueList.get(0).execute();
   colleagueList.get(4).execute();
   colleagueList.get(1).execute();
   break;
  case 4:
   colleagueList.get(0).execute();
   if (value) {
    colleagueList.get(7).execute();
    colleagueList.get(1).execute();
   } else {
    colleagueList.get(5).execute();
    colleagueList.get(2).execute();
    colleagueList.get(6).execute();
   }
   colleagueList.get(3).execute();
   break;
  default:
   System.out.println("ALERT!!! Wrong flow ID");
  }
 }
}

MediatorMain

package com.karthicksmail.designpatterns.mediator;

public class MediatorMain {
 public static void main(String[] args) {
  Mediator mediator = new Mediator();
  ConcreteColleague1 concreteColleague1 = new ConcreteColleague1();
  ConcreteColleague2 concreteColleague2 = new ConcreteColleague2();
  ConcreteColleague3 concreteColleague3 = new ConcreteColleague3();
  ConcreteColleague4 concreteColleague4 = new ConcreteColleague4();
  ConcreteColleague5 concreteColleague5 = new ConcreteColleague5();
  ConcreteColleague6 concreteColleague6 = new ConcreteColleague6();
  ConcreteColleague7 concreteColleague7 = new ConcreteColleague7();
  ConcreteColleague8 concreteColleague8 = new ConcreteColleague8();

  mediator.register(concreteColleague1);
  mediator.register(concreteColleague2);
  mediator.register(concreteColleague3);
  mediator.register(concreteColleague4);
  mediator.register(concreteColleague5);
  mediator.register(concreteColleague6);
  mediator.register(concreteColleague7);
  mediator.register(concreteColleague8);

  for (int flowId = 0; flowId < 6; flowId++) {
   System.out.println("Flow Number " + flowId);
   mediator.invokeFlow(flowId);
  }
  System.out.println("Flow Number 4");
  mediator.setValue(true);
  mediator.invokeFlow(4);
  
 }
}

Output

Registering ConcreteColleague1
Registering ConcreteColleague2
Registering ConcreteColleague3
Registering ConcreteColleague4
Registering ConcreteColleague5
Registering ConcreteColleague6
Registering ConcreteColleague7
Registering ConcreteColleague8
Flow Number 0
ALERT!!! Wrong flow ID
Flow Number 1
Executing ConcreteColleague1
Executing ConcreteColleague2
Executing ConcreteColleague3
Flow Number 2
Executing ConcreteColleague2
Executing ConcreteColleague4
Executing ConcreteColleague3
Executing ConcreteColleague6
Flow Number 3
Executing ConcreteColleague1
Executing ConcreteColleague5
Executing ConcreteColleague2
Flow Number 4
Executing ConcreteColleague1
Executing ConcreteColleague6
Executing ConcreteColleague3
Executing ConcreteColleague7
Executing ConcreteColleague4
Flow Number 5
ALERT!!! Wrong flow ID
Flow Number 4
Executing ConcreteColleague1
Executing ConcreteColleague8
Executing ConcreteColleague2
Executing ConcreteColleague4

Advantages

  1. Increases reusability by ensuring that each of the colleague classes adhere to some simple requirements only.
  2. Since all complicated code is maintained at a single class, the surface area of risk is reduced.

Disadvantages

  1. The Mediator class can become very complex. This means that any change between any unrelated use case would require all use cases to be tested. It can lead to spaghetti code in the Mediator class. But this can be handled by following clean coding practices.
Post a Comment