The mediator pattern

The mediator pattern is used to reduce the coupling between classes that communicate with each other.

Roles

This pattern constructs an object, which manages the communication between two or more classes.

These classes don't know each other's implementation. The message is sent from the class to the mediator object.

The mediator pattern defines an object that encapsulates how a set of objects will communicate with each other. Mediator promotes loose coupling by keeping objects from referring to each other explicitly and it also lets you vary their interaction independently.

The mediator is an intermediary used to decouple many peers. This pattern can be used when we want to design reusable components but dependencies between the potentially reusable pieces demonstrate the "spaghetti code" phenomenon.

Design

The following class diagram presents the generic structure of the mediator pattern:

Design

Participants

In this pattern, we find the following participants:

  • Mediator: This defines the mediator interface to communicate with elements
  • ConcreteMediator: This implements the coordination between elements and manages associations with elements
  • Elements: This is the element abstract class, which introduces common attributes, properties, and methods
  • ConcreteElement1 and ConcreteElement2: These are concretes element classes, which communicate with the mediator instead of communicating with the other elements

Collaboration

The elements send messages to the mediator and receive message from it. The mediator implements the collaboration and coordination between the elements.

Illustration

You are writing a system that allows users to communicate with each other. Communication is not sent directly from a peer to another. We will use some mediators that will manage all users and the communication between them.

For this, each user managed by the mediator will be registered (added) to the mediator. Then, when a user sends a message, we will pass the mediator object as an argument informing the system that this is the message that will broadcast that message to all other users managed by the mediator calling the receiveMessage function.

Implementation

Open the MediatorPattern Xcode project. This is a command line project that is organized as the following screenshot:

Implementation

The organization of our project is not more than what we have already described with the participants in the class diagram. We retrieve, the MediatorProtocol and ConcreteMediator object in the Mediator folder, our Elements and concrete elements UserProtocol and User are in the Elements folder and last, the main.swift file contains our client code to simulate the project.

First, we define userProtocol, in the UserProtocol.swift file:

protocol UserProtocol {
  func sendMessage(mediator:MediatorProtocol, message:AnyObject)
  func receiveMessage(message:AnyObject)
}

The sendMessage method will be used to tell the mediator passed in an argument what the message of the current concreteUser is. The receiveMessage method will be raised when the mediator broadcasts the message to all users.

Then, in the User.swift file, we implement our protocol, as shown:

class User: UserProtocol {
  var name: String
  
  init(name: String){
   self.name = name
  }
  
  func sendMessage(mediator:MediatorProtocol, message:AnyObject){
    mediator.broadcastMessage(self, message: message as AnyObject)
  }
  
  func receiveMessage(message:AnyObject){
    print("(self.name) received (String(message))")
  }
}

Here, we add an argument in the constructor to pass the name of the user.

In the sendMessage method, we see that we are calling the broadcastMessage method of the mediator passed in the argument.

When raised, the receiveMessage method will display the name of the current user and also the message that was received.

Next, lets see how MediatorProtocol is defined:

protocol MediatorProtocol {
  var users:[UserProtocol]? { get }
  
  func broadcastMessage(sender:UserProtocol, message:AnyObject)
  func register(users: UserProtocol)
}

The MediatorProtocol manages the collection of elements; here, it is Users. It can also broadcast a particular message to a user.

To add a user to the collection of users managed by the mediator, we add a Register method.

Let's see how we have implemented all of this in the Mediator.swift file:

class Mediator: MediatorProtocol {
  private let queue = dispatch_queue_create("MediatorPattern", DISPATCH_QUEUE_CONCURRENT)
  var users:[UserProtocol]? = [User]()
  
  func broadcastMessage(sender:UserProtocol, message:AnyObject){
    dispatch_barrier_sync(self.queue, { () in

      guard let users = self.users else {
        return
      }

      for u in users{
        if u as! User !== sender as! User {
          u.receiveMessage(message)
        }
      }
      
    })
  }
  func register(user: UserProtocol){
    dispatch_barrier_sync(self.queue, { () in
      users?.append(user)
    })
  }
}

First, we initialize a User array that is ready to manage a collection of user.

In the register method, we receive a user argument, which is added to the collection managed by the mediator.

Then, the broadcastMessage method:

    guard let users = users else {
      return
    }

We need to make sure that the user array has not a nil value, if this is the case we then do nothing by leaving the method invoking the return keyword.

Then, we iterate over all the users in the collection and if the current user of the iteration is different from the user sending the message (sender), then we call the receiveMessage method of the current user, along with the message to be transmitted.

Note

Concurrency protection

You probably have seen the following line:

  private let queue = dispatch_queue_create("MediatorPattern", DISPATCH_QUEUE_CONCURRENT)

We need to create concurrency protection in this pattern when various users try to access the same user at the same time, so we use a technology developed by Apple: Grand Central Dispatch which allow specific tasks in a program that can be run in parallel to be queued up for execution and, depending on availability of processing resources, scheduling them to execute on any of the available processor cores.

With this line, we initialize queue as a concurrent queue using dispatch_queue_create. The first parameter simply describes what our queue is (it could be helpful when you are debugging your code) and the second parameter specifies that we want our queue to be concurrent.

Next, we want to protect the access to our code where arrays are read and written. For this, GCD provides an elegant solution of creating read/write lock using dispatch barrier. So we use dispatch_barrier_sync to pass our queue and the statement to execute by the queue. Since, the code we have written is a barrier closure, this will never run simultaneously with any other closure in queue . To see more about Grand Central Dispatch : http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1

All our participants are now in place. We will now try all of this in the main.swift file. We create four users, each with a name:

var user1 = User(name: "Julien")
var user2 = User(name: "Helmi")
var user3 = User(name: "Adrien")
var user4 = User(name: "Raphael")

Then, we instantiate our first mediator and add the first three users we have just created to the collection of users managed by mediator1:

var mediator1 = Mediator()
mediator1.register(user1)
mediator1.register(user2)
mediator1.register(user3)

Now, we want to test that user1 can send a message to user2 and user3. We only need to invoke the sendMessage method of user1 passing the mediator1 and the message to be sent:

user1.sendMessage(mediator1, message: "message1 from (user1.name)")

So, in this case only user2 (Helmi) and user3 (Adrien) should receive the message from user1 (Julien).

We want to try the pattern with another mediator but with only two users: user2 and user4:

var mediator2 = Mediator()
mediator2.register(user2)
mediator2.register(user4)

user2.sendMessage(mediator2, message: "message 2 from (user2.name)")

Here, only user4 (Raphael) should receive the message from user2 (Helmi).

Build and run the project. You should now be able to see the following result in the console dialog:

Implementation
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset