Mediator Design Pattern in Kotlin

1. Definition

The Mediator Design Pattern aims to reduce the connections between multiple classes by centralizing external communications. It promotes the principle of "loose coupling" by ensuring that instead of classes communicating directly with each other, they communicate through a mediator object.

2. Problem Statement

Imagine you have a complex system with several interacting components. Directly interlinking these components often leads to a tightly coupled system, making it hard to change, maintain, or scale. This also violates the Single Responsibility Principle as classes handle their own logic and communication with other components.

3. Solution

The Mediator pattern introduces a central object (the mediator) that encapsulates and manages how the components interact. Each component only communicates with the mediator, reducing the dependencies between these components.

4. Real-World Use Cases

1. Air traffic control system, where the tower (mediator) manages the scheduling and communication between planes.

2. Chat application, where the server (mediator) handles the communication between clients.

3. GUI libraries, where a dialog box (mediator) coordinates interactions between GUI elements.

5. Implementation Steps

1. Define the Mediator interface.

2. Create concrete mediator classes implementing the mediator interface.

3. Create components (colleagues) that communicate with each other through the mediator instead of directly.

6. Implementation in Kotlin

// Step 1: Define the Mediator interface
interface Mediator {
    fun notify(sender: Component, event: String)
}
// Step 2: Create concrete mediator class
class ConcreteMediator : Mediator {
    var component1: Component1? = null
    var component2: Component2? = null
    override fun notify(sender: Component, event: String) {
        when (event) {
            "A" -> component2?.doB()
            "B" -> component1?.doA()
        }
    }
}
// Step 3: Create components
open class Component(protected val mediator: Mediator)
class Component1(mediator: Mediator) : Component(mediator) {
    fun doA() {
        println("Component1 does A.")
        mediator.notify(this, "A")
    }
}
class Component2(mediator: Mediator) : Component(mediator) {
    fun doB() {
        println("Component2 does B.")
        mediator.notify(this, "B")
    }
}
// Testing the implementation
fun main() {
    val mediator = ConcreteMediator()
    val c1 = Component1(mediator)
    val c2 = Component2(mediator)
    mediator.component1 = c1
    mediator.component2 = c2
    c1.doA()
    c2.doB()
}

Output:

Component1 does A.
Component2 does B.
Component2 does B.
Component1 does A.

Explanation:

1. We started by defining a Mediator interface that has a method notify(), allowing components to inform the mediator about certain events.

2. We then created a ConcreteMediator that has references to concrete components. It decides what to do when certain notifications are received.

3. Components (Component1 and Component2) perform their actions and then notify the mediator, which in turn might trigger actions in other components.

4. In our main function, we test the mediator's behavior. When Component1 triggers event A, it notifies the mediator, which then invokes a method on Component2, and vice versa.

7. When to use?

Use the Mediator Pattern when:

1. You want to reduce the number of subclassing by centralizing external behaviors.

2. You have multiple classes that are tightly coupled, leading to a spaghetti code, and you want to simplify their relationships.

3. You want to encapsulate the communication logic between various components in a single location.


Comments