Mediator Design Pattern in Kotlin

1. Definition

The Mediator Design Pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly, letting you vary their interactions independently.

2. Problem Statement

In a complex system, objects may communicate directly with many other objects, leading to a system that's hard to understand, maintain, or extend. This tight coupling between objects reduces the reusability of these objects and intertwines their logic.

3. Solution

Introduce a mediator object that centralizes external communications. Instead of objects communicating directly with each other, they communicate through the mediator. This reduces the dependencies between objects, promoting a single point of control and communication.

4. Real-World Use Cases

1. Air traffic control, coordinating multiple planes.

2. Chat room where users send messages to the room, and the room broadcasts them to all participants.

3. GUI controls, where individual components communicate via a parent control instead of directly.

5. Implementation Steps

1. Define a Mediator interface to encapsulate the interaction logic.

2. Create concrete mediator classes implementing the Mediator interface.

3. Objects, instead of communicating directly, use the mediator to communicate with other objects.

6. Implementation in Kotlin Programming

// Step 1: Define the Mediator interface
interface Mediator {
    fun notify(sender: Colleague, event: String)
}
// Step 2: Create concrete mediator class
class ConcreteMediator : Mediator {
    lateinit var colleague1: ColleagueA
    lateinit var colleague2: ColleagueB
    override fun notify(sender: Colleague, event: String) {
        when (event) {
            "A" -> colleague2.reactToA()
            "B" -> colleague1.reactToB()
        }
    }
}
// Base colleague class
abstract class Colleague(protected val mediator: Mediator)
class ColleagueA(mediator: Mediator) : Colleague(mediator) {
    fun doA() {
        println("ColleagueA triggers event A.")
        mediator.notify(this, "A")
    }
    fun reactToB() {
        println("ColleagueA reacts to event B.")
    }
}
class ColleagueB(mediator: Mediator) : Colleague(mediator) {
    fun doB() {
        println("ColleagueB triggers event B.")
        mediator.notify(this, "B")
    }
    fun reactToA() {
        println("ColleagueB reacts to event A.")
    }
}
// Client Code
fun main() {
    val mediator = ConcreteMediator()
    val colleagueA = ColleagueA(mediator)
    val colleagueB = ColleagueB(mediator)
    mediator.colleague1 = colleagueA
    mediator.colleague2 = colleagueB
    colleagueA.doA()
    colleagueB.doB()
}

Output:

ColleagueA triggers event A.
ColleagueB reacts to event A.
ColleagueB triggers event B.
ColleagueA reacts to event B.

Explanation:

1. A Mediator interface defines the contract for communication. The ConcreteMediator class implements this interface.

2. Colleague classes, ColleagueA and ColleagueB, represent components that interact with each other via the mediator.

3. Instead of directly triggering reactions in the opposite colleague, each colleague reports to the mediator, which handles the logic and coordination.

7. When to use?

Use the Mediator pattern when:

1. You want to reduce the complexity and dependencies between objects that communicate directly with many others.

2. You want to centralize external communications to promote single responsibility and control.

3. You wish to decouple classes that act together but should change independently.

Comments