Mediator
Also known as¶
- Controller
Intent¶
Reduce the complexity of communication between multiple objects by providing a centralized mediator class that handles the interactions between different classes, reducing their direct dependencies on each other.
Explanation¶
Real-world example
Imagine an air traffic control system at a busy airport, where the air traffic controller acts as a mediator. Instead of each pilot communicating directly with every other pilot, all communication goes through the air traffic controller. The controller receives requests, processes them, and gives clear, organized instructions to each pilot.
In plain words
Mediator decouples a set of classes by forcing their communications to flow through a mediating object.
Wikipedia says
In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior. With the mediator pattern, communication between objects is encapsulated within a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby reducing coupling.
Sequence diagram
sequenceDiagram
participant H as Hobbit
participant P as AdventuringParty
participant W as Wizard
participant R as Rogue
H->>P: act(ENEMY)
P->>W: partyAction(ENEMY)
P->>R: partyAction(ENEMY)
Programmatic Example¶
The party members Rogue, Wizard, Hobbit, and Hunter
all inherit from the sealed PartyMemberBase class which
implements the PartyMember interface.
interface PartyMember {
fun joinedParty(party: Party)
fun partyAction(action: Action)
fun act(action: Action)
}
sealed class PartyMemberBase : PartyMember {
private val logger = LoggerFactory.getLogger(javaClass)
private var party: Party? = null
override fun joinedParty(party: Party) {
logger.info("$this joins the party")
this.party = party
}
override fun partyAction(action: Action) {
logger.info("$this ${action.description}")
}
override fun act(action: Action) {
party?.let {
logger.info("$this $action")
it.act(this, action)
}
}
abstract override fun toString(): String
}
internal class Hobbit : PartyMemberBase() {
override fun toString(): String = "Hobbit"
}
// Hunter, Rogue, and Wizard are implemented similarly
The mediator system consists of the Party interface and its
AdventuringParty implementation.
interface Party {
fun addMember(member: PartyMember)
fun act(actor: PartyMember, action: Action)
}
internal class AdventuringParty : Party {
private val members = mutableListOf<PartyMember>()
override fun addMember(member: PartyMember) {
members.add(member)
member.joinedParty(this)
}
override fun act(actor: PartyMember, action: Action) {
members
.filter { it != actor }
.forEach { it.partyAction(action) }
}
}
Here is a demo showing the mediator pattern in action.
fun main() {
val party: Party = AdventuringParty()
val hobbit = Hobbit()
val wizard = Wizard()
val rogue = Rogue()
val hunter = Hunter()
party.addMember(hobbit)
party.addMember(wizard)
party.addMember(rogue)
party.addMember(hunter)
hobbit.act(Action.ENEMY)
wizard.act(Action.TALE)
rogue.act(Action.GOLD)
hunter.act(Action.HUNT)
}
Program output:
Hobbit joins the party
Wizard joins the party
Rogue joins the party
Hunter joins the party
Hobbit spotted enemies
Wizard runs for cover
Rogue runs for cover
Hunter runs for cover
Wizard tells a tale
Hobbit comes to listen
Rogue comes to listen
Hunter comes to listen
Rogue found gold
Hobbit takes his share of the gold
Wizard takes his share of the gold
Hunter takes his share of the gold
Hunter hunted a rabbit
Hobbit arrives for dinner
Wizard arrives for dinner
Rogue arrives for dinner
Class diagram¶
classDiagram
class Party {
<<interface>>
+addMember(member: PartyMember)
+act(actor: PartyMember, action: Action)
}
class AdventuringParty {
-members: MutableList~PartyMember~
+addMember(member: PartyMember)
+act(actor: PartyMember, action: Action)
}
class PartyMember {
<<interface>>
+joinedParty(party: Party)
+partyAction(action: Action)
+act(action: Action)
}
class PartyMemberBase {
<<sealed>>
-party: Party?
+joinedParty(party: Party)
+partyAction(action: Action)
+act(action: Action)
+toString(): String*
}
class Hobbit {
+toString(): String
}
class Hunter {
+toString(): String
}
class Rogue {
+toString(): String
}
class Wizard {
+toString(): String
}
class Action {
<<enumeration>>
ENEMY
GOLD
HUNT
NONE
TALE
+description: String
+toString(): String
}
Party <|.. AdventuringParty
PartyMember <|.. PartyMemberBase
PartyMemberBase <|-- Hobbit
PartyMemberBase <|-- Hunter
PartyMemberBase <|-- Rogue
PartyMemberBase <|-- Wizard
AdventuringParty --> PartyMember
PartyMemberBase --> Party
PartyMemberBase --> Action
Applicability¶
Use the Mediator pattern when:
- A set of objects communicate in well-defined but complex ways and the resulting interdependencies are unstructured and difficult to understand
- Reusing an object is difficult because it refers to and communicates with many other objects
- A behavior that is distributed between several classes should be customizable without a lot of subclassing
Consequences¶
Benefits:
- Reduces coupling between components of a program, fostering better organization and easier maintenance
- Centralizes control: the mediator pattern centralizes the control logic, making it easier to comprehend and manage
Trade-offs:
- The mediator can become a god object coupled with all classes in the system, gaining too much responsibility and complexity