The mediator pattern is used to reduce the coupling between classes that communicate with each other.
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.
In this pattern, we find the following participants:
Mediator
: This defines the mediator interface to communicate with elementsConcreteMediator
: This implements the coordination between elements and manages associations with elementsElements
: This is the element abstract class, which introduces common attributes, properties, and methodsConcreteElement1
and ConcreteElement2
: These are concretes element classes, which communicate with the mediator instead of communicating with the other elementsThe elements send messages to the mediator and receive message from it. The mediator implements the collaboration and coordination between the elements.
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.
Open the MediatorPattern
Xcode project. This is a command line project that is organized as the following screenshot:
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.
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: