What Is It?
The Mediator pattern replaces a tangle of object-to-object references with a single hub. Each colleague knows only the mediator; when it wants something, it asks the mediator, and the mediator decides who else needs to hear about it.
Picture an airport. Pilots do not radio each other to negotiate who lands first — they all talk to the control tower. The tower holds the shared rule (one plane on the runway at a time) and tells everyone else to hold. Add a fifth plane and nothing changes for the other four.
In Unity this shows up as a ControlTower MonoBehaviour that Aircraft components register with in OnEnable. Aircraft call tower.RequestLanding(this); the tower grants or denies, and broadcasts runway status to the rest of the fleet.
When Is It Used?
Use it when a group of objects interact in many-to-many ways and the wiring is getting out of hand: UI panels that enable and disable each other, squad AI that coordinates targets, or puzzle pieces that react to one another.
It is a good home for shared rules that do not belong to any single participant, such as “only one door can be open” or “only one unit may attack this target”.
Avoid it when communication is genuinely one-to-many with no coordination logic — a plain event (Observer) is simpler. And watch for the mediator turning into a god object that knows too much.
Interactive Demo
Click the controls and watch the objects collaborate. The console mirrors what the C# code below would log.
Code
using UnityEngine;
namespace Patterns.Behavioral.Mediator
{
/// <summary>
/// Client: asks individual aircraft to land. All coordination
/// happens inside the ControlTower.
/// </summary>
public class AirportDemo : MonoBehaviour
{
[SerializeField] private Aircraft[] aircraft;
private void Update()
{
for (int i = 0; i < aircraft.Length && i < 9; i++)
{
// Keys 1..9 request landing for each plane.
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
aircraft[i].TryLand();
}
}
}
}namespace Patterns.Behavioral.Mediator
{
/// <summary>
/// The mediator contract. Aircraft only ever see this interface,
/// never each other.
/// </summary>
public interface IAirTrafficMediator
{
void Register(Aircraft aircraft);
void Unregister(Aircraft aircraft);
/// <summary>Asks for the runway. Returns true if landing is cleared.</summary>
bool RequestLanding(Aircraft aircraft);
/// <summary>Called by the aircraft once it has vacated the runway.</summary>
void ReportRunwayClear(Aircraft aircraft);
}
}using System.Collections.Generic;
using UnityEngine;
namespace Patterns.Behavioral.Mediator
{
/// <summary>
/// Concrete mediator. Owns the shared runway rule and relays
/// status messages to every other registered aircraft.
/// </summary>
public class ControlTower : MonoBehaviour, IAirTrafficMediator
{
private readonly List<Aircraft> fleet = new();
private Aircraft onRunway;
public void Register(Aircraft aircraft)
{
if (!fleet.Contains(aircraft))
fleet.Add(aircraft);
}
public void Unregister(Aircraft aircraft)
{
fleet.Remove(aircraft);
if (onRunway == aircraft)
onRunway = null;
}
public bool RequestLanding(Aircraft aircraft)
{
if (onRunway != null)
{
Debug.Log($"Tower: {aircraft.CallSign}, hold. Runway occupied by {onRunway.CallSign}.");
return false;
}
onRunway = aircraft;
Broadcast(aircraft, $"{aircraft.CallSign} is landing. Hold your position.");
return true;
}
public void ReportRunwayClear(Aircraft aircraft)
{
if (onRunway != aircraft) return;
onRunway = null;
Broadcast(aircraft, "Runway is clear.");
}
private void Broadcast(Aircraft sender, string message)
{
foreach (Aircraft other in fleet)
{
if (other != sender)
other.Receive(message);
}
}
}
}using System.Collections;
using UnityEngine;
namespace Patterns.Behavioral.Mediator
{
/// <summary>
/// Colleague. Knows the tower, never another aircraft.
/// </summary>
public class Aircraft : MonoBehaviour
{
[SerializeField] private string callSign = "UA-101";
[SerializeField] private ControlTower tower;
[SerializeField] private float landingDuration = 3f;
public string CallSign => callSign;
public bool IsLanding { get; private set; }
private IAirTrafficMediator Mediator => tower;
private void OnEnable() => Mediator.Register(this);
private void OnDisable() => Mediator.Unregister(this);
public void TryLand()
{
if (IsLanding) return;
if (Mediator.RequestLanding(this))
StartCoroutine(LandRoutine());
}
public void Receive(string message)
{
Debug.Log($"{callSign} hears tower: {message}");
}
private IEnumerator LandRoutine()
{
IsLanding = true;
yield return new WaitForSeconds(landingDuration);
IsLanding = false;
Mediator.ReportRunwayClear(this);
}
}
}Advantages & Disadvantages
+ Advantages
- Colleagues are decoupled from each other; you can add or remove one without touching the rest.
- Interaction rules live in one place, so they are easy to find, change and test.
- Turns an N×N web of references into N links to a single hub.
− Disadvantages
- The mediator can grow into a god class that is hard to maintain.
- Everything goes through one object, so a bug there affects every participant.
- Indirection makes it less obvious, at a glance, which objects end up affected by a call.
Tips
- 01Define an
IMediatorinterface so colleagues can be tested with a fake mediator. - 02Register and unregister colleagues in
OnEnable/OnDisable, just like event subscriptions. - 03If one mediator becomes too large, split it by concern (e.g.
RunwayControllerandRadioChannel) rather than letting colleagues talk directly again. - 04Mediator and Observer combine well: the mediator can raise C# events internally while still owning the coordination rules.
- 05Keep colleague-to-mediator calls intention-revealing (
RequestLanding,ReportClear) rather than a genericSend(string), so the compiler helps you.