Thursday, October 25, 2012

Clarifying Mediator pattern

 

 

Introduction

Mediator is design pattern that is primarily used to loose coupling between components in their communication. For instance, if each of components would communicate to each other directly, we could get a complex net of communication lines between these components. Instead of direct communication we are using mediator who will be intermediate component for inside communication between components.
Simple scenario for mediator role would be to gather messages from each component (component is in some books referred as Colleague), and than mediator would broadcast that message to all other components.
In the next image we can see UML class diagram showing us dependencies between classes in implementation of Mediator pattern. Mediator and Component can be either abstract classes or interfaces, depending of the specific implementation.

 
ClassDiagram
UML class diagram representing Mediator pattern
 
Now, there is a question, since mediator using broadcast, how each of components would know that the message is intended just for her. Well, in most cases message contains a key value that would be known to the receiver and sender, and it will be used to determine the originator and receiver of the message. There is also possibility to send sender and recipient information within the message. So, when the message is received by some component, component can determine if the message is intended for her or some other component, in that way message can be processed or ignored.
From the security side, this is not very safe way to distribute all messages as a broadcast, but if implementation is not restricted with security rules (for example, UI classes) than it can have a good purpose to decouple communication between components.

Implementation

In this blog I’ll demonstrate simple chat application written in Java. Application will contain three tabs (three components, i.e. Colleague), and each of the tabs will be able to communicate with other tabs over the Mediator. For the message representation I’ll implement Intent class which instances will be able to contain string messages. Of course, current implementation of Intent can be extended and used to wrap other object types in a message as well.

Mediator demo app
Screenshot of the Mediator demo application

Current implementation of demo project is demonstrated in the next picture:

NewDiagram
UML class diagram representing implementation of Mediator pattern
 

Source code from demo project can be downloaded from here.


Now, let’s take a look at the code.
This is simple Mediator interface that will be implemented in the Mediator class.

package com.testmediator;
public interface IMediator {
  public void addComponent(IComponent _component);
  public void send(Intent _message, IComponent _sender);
}

Class Mediator implements previous interface with two methods. Method addComponent is used for registering components, since the Mediator needs to know all actors of communication. Second method, send, simply broadcasts message to all of the components, except sender component.
package com.testmediator;
import java.util.ArrayList;
public class Mediator implements IMediator{
  private ArrayList<IComponent> components = new ArrayList<IComponent>();
  
  @Override
  public void addComponent(IComponent _component) {
    components.add(_component);
  }
  @Override
  public void send(Intent _message, IComponent _sender) {
    for(IComponent component : components){
      if(!component.equals(_sender)){
        component.receive(_message);
      }
    }
  }
}

In the next code preview you can see Component interface that will be implemented in Component classes.
package com.testmediator;
public interface IComponent {
  public void receive(Intent _message);
  public void send(Intent _message, IComponent _sender);
  public IMediator getMediator();
}

As you can see in the Component implementation, each Component contains the reference to the same Mediator that is set up in the constructor. Other important methods are send and receive which implements sending message via Mediator instance and receiving message from Mediator broadcast, respectively. On the click of the send button message will be send to another component depending of selected recipient from the combo box.
package com.testmediator;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class ComponentOne extends WindowAdapter implements IComponent, ActionListener{
  private IMediator m_mediator = null;
  private JPanel m_panel = null;
  private JComboBox<String> m_tabSelect = null;
  private JLabel m_label = null;
  private JTextField m_text = null;
  private JButton m_button = null;
  private JLabel m_receivedMsg = null;
  private Intent m_message = null;
  
  public ComponentOne(IMediator mediator){
    m_mediator = mediator;
    initGui();
  }
  
  @Override
  public void receive(Intent _message) {
    m_message = _message;
    if(m_message.getStringMsg("ToTab01")!=""){
      m_receivedMsg.setText(m_receivedMsg.getText() + " - " + m_message.getStringMsg("ToTab01"));
    }
    m_panel.repaint();
  }
  @Override
  public void send(Intent _message, IComponent _sender) {
    m_mediator.send(_message, this);
  }
  @Override
  public IMediator getMediator() {
    return m_mediator;
  }
  
  private void initGui(){
    m_panel = new JPanel();
    
    m_tabSelect = new JComboBox<String>();
    m_tabSelect.addItem("tab02");
    m_tabSelect.addItem("tab03");
    m_label = new JLabel("Tab01, enter the message: ");
    m_text = new JTextField(20);
    m_button = new JButton("Send");
    m_button.addActionListener(this);
    m_receivedMsg = new JLabel("Received: ");
    
    m_panel.add(m_tabSelect);
    m_panel.add(m_label);
    m_panel.add(m_text);
    m_panel.add(m_button);
    m_panel.add(m_receivedMsg);
  }
  
  public JPanel getPanel(){
    return m_panel;
  }
  
  public Intent getMessage(){
    return m_message;
  }
  @Override
  public void actionPerformed(ActionEvent _event) {
    Object source = _event.getSource();
    if(source.equals(m_button)){
      Intent intent = new Intent();
      if(m_tabSelect.getSelectedItem() == "tab02"){
        intent.putStringMsg("ToTab02", m_text.getText());
      }else if(m_tabSelect.getSelectedItem() == "tab03"){
        intent.putStringMsg("ToTab03", m_text.getText());
      }
      send(intent,this);
    }
  }
}

Simple Intent class represents wrapper for all messages. As you can see for now it contains only methods for putting string message and reading the same message. This class can be extended to wrap all other kinds of objects.
package com.testmediator;
public class Intent {
  String m_stringMsgKey;
  String m_stringMsg = "";
  public void putStringMsg(String _key, String _value){
    m_stringMsgKey = _key;
    m_stringMsg = _value;
  }
  
  public String getStringMsg(String _messageKey){
    if(_messageKey == m_stringMsgKey){
      return m_stringMsg;
    }
    else{
      return "";
    }
  }
}

Other classes from project are excluded from code preview since primary role of this blog was to present Mediator pattern. In a context of the project it is basically a simple desktop app developed using swing library.


Conclusion


Mediator pattern is pretty common to meet in a different solutions. For instance, Android platform is using very similar principle regarding communication between Activities using Intent objects as messages that will be broadcasted to all Activity instances in the app. This way complex peer-to-peer communication is replaced by the Mediator who is in charge of distributing messages, providing loose coupling between components which is in the almost all cases very good practice.

No comments:

Post a Comment