Skip to content

Mediator Design Pattern

Video Lecture

Section Video Links
Mediator Pattern Mediator Mediator Pattern  
Mediator Use Case Mediator Use Case Mediator Use Case  

Overview

Objects communicate through the Mediator rather than directly with each other.

As a system evolves and becomes larger and supports more complex functionality and business rules, the problem of communicating between these components becomes more complicated to understand and manage. It may be beneficial to refactor your system to centralize some or all of its functionality via some kind of mediation process.

The mediator pattern is similar to implementing the Facade pattern between your objects and processes. Except that the structure of the Mediator could also allow multi-directional communication between each component and provide the opportunity to add some logic to the messaging flow to make it more cooperative in some way. E.g., managing the routing behavior by serializing or batching messages, the centralization of application logic, caching, logging, etc.

Terminology

  • Mediator: The coordinator of communications between the components (colleagues).
  • Colleagues: One of the many types of concrete components that use the mediator.

Mediator UML Diagram

Mediator Pattern UML Diagram

Source Code

In the example concept, there are two colleague classes that use each other's methods. Instead of the Colleagues calling each other's methods directly, they implement the Mediator interface and call each other via the Mediator. Each colleague is designed for a different purpose, but they utilize some related functionality from each other.

The system, in this case, would work without the Mediator, but adding the Mediator would allow extending functionality to a potential third colleague that provides a different service, such as AI analysis or monitoring, without needing to add specific support or knowledge into the two original colleagues.

In this first example the Mediator is structurally acting as a multi-directional relay between the two colleagues.

./src/mediator/mediator-concept.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Mediator Concept Sample Code

class Mediator {
    // The Mediator Concrete Class
    colleague1: Colleague1
    colleague2: Colleague2

    constructor() {
        this.colleague1 = new Colleague1()
        this.colleague2 = new Colleague2()
    }

    colleague1Method() {
        // Calls the method provided by Colleague1
        return this.colleague1.method1()
    }

    colleague2Method() {
        // Calls the method provided by Colleague2
        return this.colleague2.method2()
    }
}

class Colleague1 {
    // This Colleague provides data for Colleague2

    method1() {
        return 'Here is the Colleague1 specific data you asked for'
    }
}

class Colleague2 {
    // This Colleague provides data for Colleague1

    method2() {
        return 'Here is the Colleague2 specific data you asked for'
    }
}

// The Client
const MEDIATOR = new Mediator()

// Colleague1 wants some data from Colleague2
let DATA = MEDIATOR.colleague2Method()
console.log(`COLLEAGUE1 <--> ${DATA}`)

// Colleague2 wants some data from Colleague1
DATA = MEDIATOR.colleague1Method()
console.log(`COLLEAGUE2 <--> ${DATA}`)

Output

node ./dist/mediator/mediator-concept.js
COLLEAGUE1 <--> Here is the Colleague2 specific data you asked for
COLLEAGUE2 <--> Here is the Colleague1 specific data you asked for

Mediator Use Case

In this example use case, we will implement some behavior into the mediation process.

Before the mediation logic is added, consider that the below example is a series of components all subscribed to a central location being the subject. They all implement the Observer pattern.

Each component is updated independently by external forces, but when it has new information, it notifies the subject which in turn then notifies the other subscribed components.

During the synchronization of all the subscribed components, without the extra mediation, the component that provided the new information will receive back the same message that it just notified the subject of. In order to manage the unnecessary duplicate message, the notifications will be mediated to exclude to component where the original message originated from.

Example UML Diagram

Mediator Pattern UML Diagram

Source Code

./src/mediator/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// The Mediator Use Case Example

import Component from './component'
import Mediator from './mediator'

const MEDIATOR = new Mediator()
const COMPONENT1 = new Component(MEDIATOR, 'Component1')
const COMPONENT2 = new Component(MEDIATOR, 'Component2')
const COMPONENT3 = new Component(MEDIATOR, 'Component3')

MEDIATOR.add(COMPONENT1)
MEDIATOR.add(COMPONENT2)
MEDIATOR.add(COMPONENT3)

COMPONENT1.notify('data A')
COMPONENT2.notify('data B')
COMPONENT3.notify('data C')

./src/mediator/component.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Each component stays synchronized through a mediator

import IComponent from './icomponent'
import Mediator from './mediator'

export default class Component implements IComponent {
    #mediator: Mediator
    #name: string

    constructor(mediator: Mediator, name: string) {
        this.#mediator = mediator
        this.#name = name
    }

    notify(message: string): void {
        console.log(this.#name + ': >>> Out >>> : ' + message)
        this.#mediator.notify(message, this)
    }

    receive(message: string): void {
        console.log(this.#name + ': <<< In <<< : ' + message)
    }
}

./src/mediator/icomponent.ts

1
2
3
4
5
6
7
// An interface that each component should implement

export default interface IComponent {
    notify(message: string): void

    receive(message: string): void
}

./src/mediator/mediator.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// The Subject that all components will stay synchronized with

import IComponent from './icomponent'

export default class Mediator {
    // A Subject whose notify method is mediated
    #components: Set<IComponent>

    constructor() {
        this.#components = new Set()
    }

    add(component: IComponent): void {
        // Add components
        this.#components.add(component)
    }

    notify(message: string, originator: IComponent): void {
        // Add components except for the originator component
        this.#components.forEach((component) => {
            if (component !== originator) {
                component.receive(message)
            }
        })
    }
}

Output

node ./dist/mediator/client.js
Component1: >>> Out >>> : data A
Component2: <<< In <<< : data A
Component3: <<< In <<< : data A
Component2: >>> Out >>> : data B
Component1: <<< In <<< : data B
Component3: <<< In <<< : data B
Component3: >>> Out >>> : data C
Component1: <<< In <<< : data C
Component2: <<< In <<< : data C

Summary

  • A mediator replaces a structure with many-to-many interactions between its classes and processes, with a one-to-many centralized structure where the interface supports all the methods of the many-to-many structure, but via the mediator component instead.
  • The mediator pattern encourages usage of shared objects that can now be centrally managed and synchronized.
  • The mediator pattern creates an abstraction between two or more components that then makes a system easier to understand and manage.
  • The mediator pattern is similar to the Facade pattern, except the Mediator can also transact data both ways between two or more other classes or processes that would normally interact directly with each other.