BrandGhost
Mediator Pattern Real-World Example in C#: Complete Implementation

Mediator Pattern Real-World Example in C#: Complete Implementation

Mediator Pattern Real-World Example in C#: Complete Implementation

Most mediator pattern tutorials show you a chat room where User A sends a string to User B and call it done. That's enough to understand the concept but nowhere near enough to build a system where smart home devices need to coordinate without knowing about each other. This article builds a complete mediator pattern real-world example in C# from the ground up: a smart home automation system where lights, thermostats, security cameras, and music players coordinate their behavior through a central home automation mediator -- without any device holding a direct reference to another.

By the end, you'll have a full set of compilable classes covering the mediator interface design, concrete device implementations, the home automation mediator that orchestrates cross-device coordination, unit tests that verify each piece in isolation, and a demonstration of how adding new devices requires zero changes to existing code. If you've been looking for a practical mediator pattern implementation that goes beyond the textbook, this is the article.

The Problem: Device Coordination Without the Mediator Pattern

Consider a smart home system where devices need to react to each other. When the security system arms itself, the lights should dim, the thermostat should enter energy-saving mode, and the music should stop. When someone arrives home and disarms security, the reverse should happen. Without the mediator pattern, every device has to know about every other device:

public class SecuritySystem
{
    private readonly LightController _lights;
    private readonly Thermostat _thermostat;
    private readonly MusicPlayer _music;

    public SecuritySystem(
        LightController lights,
        Thermostat thermostat,
        MusicPlayer music)
    {
        _lights = lights;
        _thermostat = thermostat;
        _music = music;
    }

    public void Arm()
    {
        IsArmed = true;

        // Security knows about lights
        _lights.SetBrightness(10);

        // Security knows about thermostat
        _thermostat.SetMode("eco");

        // Security knows about music
        _music.Stop();
    }

    public bool IsArmed { get; private set; }
}

This class violates the Single Responsibility Principle in a painful way. The SecuritySystem has to know about light brightness levels, thermostat mode strings, and music playback control. Every time you add a new device -- say, a smart lock or a robot vacuum -- you modify SecuritySystem and every other device that needs to react to it. You end up with an N-by-N web of dependencies where every device talks to every other device directly.

Testing becomes a nightmare. Verifying that arming the security system sets the correct thermostat mode requires mocking lights and music too, even though they're irrelevant to the thermostat assertion. And if you want to change the "arm" behavior -- maybe the lights should turn off entirely instead of dimming -- you're editing a security class to fix a lighting concern. The mediator pattern eliminates this coupling by routing all cross-device communication through a single coordinator.

Designing the Mediator Pattern Interfaces

Every mediator pattern real-world example in C# begins with two clean abstractions: a mediator interface that handles coordination and a colleague interface for the participants. We also need a notification object to describe what happened. Let's design all three:

namespace SmartHome.Core;

public sealed record DeviceNotification(
    string DeviceId,
    string EventType,
    Dictionary<string, object> Payload)
{
    public T GetPayloadValue<T>(string key)
    {
        if (Payload.TryGetValue(key, out var value)
            && value is T typed)
        {
            return typed;
        }

        throw new InvalidOperationException(
            $"Payload key '{key}' not found " +
            $"or not of type {typeof(T).Name}.");
    }
}

The DeviceNotification record carries the identity of the device that raised the event, what kind of event occurred, and a flexible payload dictionary. This keeps the mediator pattern's communication channel generic enough that any device can send any event without requiring new message types for every interaction.

Next, the two core interfaces that define how the mediator pattern participants interact:

namespace SmartHome.Core;

public interface ISmartHomeMediator
{
    void RegisterDevice(ISmartDevice device);

    void Notify(
        ISmartDevice sender,
        DeviceNotification notification);
}

public interface ISmartDevice
{
    string DeviceId { get; }

    string DeviceType { get; }

    void SetMediator(ISmartHomeMediator mediator);

    void ReceiveNotification(
        DeviceNotification notification);
}

The ISmartHomeMediator has two responsibilities: registering devices and routing notifications. When a device does something noteworthy, it calls Notify on its mediator. The mediator then decides which other devices need to know and calls ReceiveNotification on each one. No device ever references another device directly. This is the core of the mediator pattern -- all communication flows through the central coordinator.

Notice that ISmartDevice includes a SetMediator method. This lets devices send notifications back to the mediator when they need to broadcast their own state changes. It's a common approach in mediator pattern implementations where colleagues need to initiate communication, not just receive it.

Building a Base Device Class

Before implementing individual devices, let's extract the common mediator pattern wiring into a base class. Every device needs to store its mediator reference and provide a helper for sending notifications:

namespace SmartHome.Core;

public abstract class SmartDeviceBase : ISmartDevice
{
    private ISmartHomeMediator? _mediator;

    protected SmartDeviceBase(
        string deviceId,
        string deviceType)
    {
        DeviceId = deviceId;
        DeviceType = deviceType;
    }

    public string DeviceId { get; }

    public string DeviceType { get; }

    public void SetMediator(
        ISmartHomeMediator mediator)
    {
        _mediator = mediator;
    }

    public abstract void ReceiveNotification(
        DeviceNotification notification);

    protected void SendNotification(
        string eventType,
        Dictionary<string, object>? payload = null)
    {
        _mediator?.Notify(
            this,
            new DeviceNotification(
                DeviceId,
                eventType,
                payload ?? new Dictionary<string, object>()));
    }
}

The SmartDeviceBase class handles the mediator pattern plumbing so that concrete devices only need to implement their specific behavior. The SendNotification helper wraps the mediator call, building the DeviceNotification from the device's own identity. If you've worked with the observer design pattern, you'll notice a key difference: in the observer pattern, subjects broadcast to a list of subscribers directly. In the mediator pattern, all communication routes through a central object that decides who receives what.

Implementing the Smart Home Devices

Now let's build four concrete devices. Each one handles its own responsibilities and communicates exclusively through the mediator pattern.

The Light Controller

The light controller manages brightness and on/off state. It reacts to security events and occupancy changes:

namespace SmartHome.Devices;

public sealed class LightController : SmartDeviceBase
{
    public LightController(string deviceId)
        : base(deviceId, "Light")
    {
    }

    public int Brightness { get; private set; } = 100;

    public bool IsOn { get; private set; } = true;

    public void SetBrightness(int level)
    {
        Brightness = Math.Clamp(level, 0, 100);
        IsOn = Brightness > 0;

        SendNotification(
            "brightness_changed",
            new Dictionary<string, object>
            {
                ["brightness"] = Brightness
            });
    }

    public void TurnOff()
    {
        SetBrightness(0);
    }

    public void TurnOn(int brightness = 100)
    {
        SetBrightness(brightness);
    }

    public override void ReceiveNotification(
        DeviceNotification notification)
    {
        switch (notification.EventType)
        {
            case "security_armed":
                SetBrightness(10);
                break;

            case "security_disarmed":
                SetBrightness(100);
                break;

            case "motion_detected":
                if (!IsOn)
                {
                    TurnOn(70);
                }
                break;
        }
    }
}

The Smart Thermostat

The thermostat manages temperature and operating mode. It reacts to security events and can trigger alerts when temperatures hit extremes:

namespace SmartHome.Devices;

public sealed class SmartThermostat : SmartDeviceBase
{
    public SmartThermostat(string deviceId)
        : base(deviceId, "Thermostat")
    {
    }

    public double TargetTemperature { get; private set; }
        = 72.0;

    public string Mode { get; private set; } = "comfort";

    public void SetTemperature(double temperature)
    {
        TargetTemperature = temperature;

        SendNotification(
            "temperature_changed",
            new Dictionary<string, object>
            {
                ["temperature"] = TargetTemperature,
                ["mode"] = Mode
            });
    }

    public void SetMode(string mode)
    {
        Mode = mode;

        TargetTemperature = mode switch
        {
            "eco" => 65.0,
            "comfort" => 72.0,
            "away" => 60.0,
            _ => TargetTemperature
        };

        SendNotification(
            "thermostat_mode_changed",
            new Dictionary<string, object>
            {
                ["mode"] = Mode,
                ["temperature"] = TargetTemperature
            });
    }

    public override void ReceiveNotification(
        DeviceNotification notification)
    {
        switch (notification.EventType)
        {
            case "security_armed":
                SetMode("away");
                break;

            case "security_disarmed":
                SetMode("comfort");
                break;
        }
    }
}

The Security System

The security system monitors the home and broadcasts arm/disarm events. Other devices react to these events through the mediator pattern without the security system knowing anything about lights, thermostats, or music:

namespace SmartHome.Devices;

public sealed class SecuritySystem : SmartDeviceBase
{
    public SecuritySystem(string deviceId)
        : base(deviceId, "Security")
    {
    }

    public bool IsArmed { get; private set; }

    public List<string> AlertLog { get; } = new();

    public void Arm()
    {
        IsArmed = true;

        SendNotification(
            "security_armed",
            new Dictionary<string, object>
            {
                ["armed"] = true
            });
    }

    public void Disarm()
    {
        IsArmed = false;

        SendNotification(
            "security_disarmed",
            new Dictionary<string, object>
            {
                ["armed"] = false
            });
    }

    public void DetectMotion(string zone)
    {
        if (IsArmed)
        {
            AlertLog.Add($"Motion in {zone}");

            SendNotification(
                "motion_detected",
                new Dictionary<string, object>
                {
                    ["zone"] = zone,
                    ["alert"] = true
                });
        }
    }

    public override void ReceiveNotification(
        DeviceNotification notification)
    {
        if (notification.EventType
            == "temperature_changed")
        {
            var temp = notification
                .GetPayloadValue<double>("temperature");
            if (temp > 95.0)
            {
                AlertLog.Add(
                    "High temperature alert: " +
                    $"{temp}°F");
            }
        }
    }
}

The Music Player

The music player handles playback state. It pauses when security arms and can resume when the system disarms:

namespace SmartHome.Devices;

public sealed class MusicPlayer : SmartDeviceBase
{
    public MusicPlayer(string deviceId)
        : base(deviceId, "Music")
    {
    }

    public bool IsPlaying { get; private set; }

    public string CurrentTrack { get; private set; }
        = string.Empty;

    public int Volume { get; private set; } = 50;

    public void Play(string track)
    {
        CurrentTrack = track;
        IsPlaying = true;

        SendNotification(
            "music_started",
            new Dictionary<string, object>
            {
                ["track"] = track
            });
    }

    public void Pause()
    {
        IsPlaying = false;

        SendNotification("music_paused",
            new Dictionary<string, object>());
    }

    public void SetVolume(int volume)
    {
        Volume = Math.Clamp(volume, 0, 100);
    }

    public override void ReceiveNotification(
        DeviceNotification notification)
    {
        switch (notification.EventType)
        {
            case "security_armed":
                if (IsPlaying)
                {
                    Pause();
                }
                break;

            case "security_disarmed":
                if (!IsPlaying
                    && !string.IsNullOrEmpty(CurrentTrack))
                {
                    Play(CurrentTrack);
                }
                break;

            case "brightness_changed":
                var brightness = notification
                    .GetPayloadValue<int>("brightness");
                if (brightness < 30)
                {
                    SetVolume(
                        Math.Min(Volume, 30));
                }
                break;
        }
    }
}

Notice how each device only cares about the events relevant to it. The MusicPlayer doesn't know that a SecuritySystem exists -- it just knows how to react to a "security_armed" event. This is the mediator pattern doing its job. The devices are loosely coupled, and each one can be tested, modified, or replaced independently. If you're familiar with the command design pattern, you'll recognize a similar emphasis on decoupling the "what happened" from "what to do about it."

Implementing the Home Automation Mediator

Now for the centerpiece of this mediator pattern example -- the mediator itself. The HomeAutomationMediator registers devices, receives notifications, and forwards them to all other registered devices:

namespace SmartHome.Core;

public sealed class HomeAutomationMediator
    : ISmartHomeMediator
{
    private readonly List<ISmartDevice> _devices = new();

    public IReadOnlyList<ISmartDevice> Devices
        => _devices.AsReadOnly();

    public void RegisterDevice(ISmartDevice device)
    {
        if (_devices.Any(d =>
            d.DeviceId == device.DeviceId))
        {
            throw new InvalidOperationException(
                $"Device '{device.DeviceId}' " +
                "is already registered.");
        }

        _devices.Add(device);
        device.SetMediator(this);
    }

    public void Notify(
        ISmartDevice sender,
        DeviceNotification notification)
    {
        foreach (var device in _devices)
        {
            if (device.DeviceId != sender.DeviceId)
            {
                device.ReceiveNotification(notification);
            }
        }
    }
}

The mediator forwards each notification to every device except the sender. This is a broadcast approach -- every device gets every event and decides internally whether to react. The alternative is a routing approach where the mediator contains conditional logic about which devices should receive which events. The broadcast approach keeps the mediator simple and pushes decision-making to the devices, which is the more extensible choice when you're building a system that will grow over time.

This implementation demonstrates why the mediator pattern is powerful. Compare this to the original SecuritySystem class that had direct references to three other devices. The mediator contains zero knowledge about what lights, thermostats, or music players do. It just routes messages. If you're familiar with inversion of control, you'll recognize the principle at work here -- the mediator depends on the abstract ISmartDevice interface, not on any concrete device.

Testing the Mediator Pattern Implementation

Testability is one of the biggest practical benefits of the mediator pattern. Because devices communicate through the mediator interface rather than holding direct references to each other, you can test each device in isolation. Here's a complete test class using xUnit:

using SmartHome.Core;
using SmartHome.Devices;

using Xunit;

namespace SmartHome.Tests;

public class HomeAutomationMediatorTests
{
    private readonly HomeAutomationMediator _mediator;
    private readonly LightController _lights;
    private readonly SmartThermostat _thermostat;
    private readonly SecuritySystem _security;
    private readonly MusicPlayer _music;

    public HomeAutomationMediatorTests()
    {
        _mediator = new HomeAutomationMediator();
        _lights = new LightController("light-01");
        _thermostat = new SmartThermostat("thermo-01");
        _security = new SecuritySystem("security-01");
        _music = new MusicPlayer("music-01");

        _mediator.RegisterDevice(_lights);
        _mediator.RegisterDevice(_thermostat);
        _mediator.RegisterDevice(_security);
        _mediator.RegisterDevice(_music);
    }

    [Fact]
    public void Arm_SecurityArmed_LightsDimToTen()
    {
        _security.Arm();

        Assert.Equal(10, _lights.Brightness);
    }

    [Fact]
    public void Arm_SecurityArmed_ThermostatSwitchesToAway()
    {
        _security.Arm();

        Assert.Equal("away", _thermostat.Mode);
        Assert.Equal(60.0, _thermostat.TargetTemperature);
    }

    [Fact]
    public void Arm_MusicPlaying_MusicPauses()
    {
        _music.Play("Ambient Vibes");

        _security.Arm();

        Assert.False(_music.IsPlaying);
    }

    [Fact]
    public void Disarm_AfterArming_LightsRestoreFullBrightness()
    {
        _security.Arm();

        _security.Disarm();

        Assert.Equal(100, _lights.Brightness);
    }

    [Fact]
    public void Disarm_MusicWasPaused_MusicResumes()
    {
        _music.Play("Evening Jazz");
        _security.Arm();

        _security.Disarm();

        Assert.True(_music.IsPlaying);
        Assert.Equal("Evening Jazz", _music.CurrentTrack);
    }

    [Fact]
    public void Disarm_SecurityDisarmed_ThermostatResumesComfort()
    {
        _security.Arm();

        _security.Disarm();

        Assert.Equal("comfort", _thermostat.Mode);
        Assert.Equal(72.0, _thermostat.TargetTemperature);
    }

    [Fact]
    public void RegisterDevice_DuplicateId_ThrowsException()
    {
        var duplicate = new LightController("light-01");

        Assert.Throws<InvalidOperationException>(
            () => _mediator.RegisterDevice(duplicate));
    }
}

Each test exercises a specific cross-device interaction through the mediator pattern. The Arm_SecurityArmed_LightsDimToTen test verifies that arming the security system causes the lights to dim -- without the test needing to know how the notification flows. The mediator pattern makes this possible because the test sets up the mediator, registers devices, triggers an action, and asserts the outcome. No mocking required for these integration-level tests because the devices are lightweight and deterministic.

You can also test individual devices in complete isolation by sending them notifications directly:

public class LightControllerTests
{
    [Fact]
    public void ReceiveNotification_SecurityArmed_DimToTen()
    {
        var light = new LightController("light-test");

        var notification = new DeviceNotification(
            "security-01",
            "security_armed",
            new Dictionary<string, object>
            {
                ["armed"] = true
            });

        light.ReceiveNotification(notification);

        Assert.Equal(10, light.Brightness);
    }

    [Fact]
    public void ReceiveNotification_MotionDetected_TurnsOnWhenOff()
    {
        var light = new LightController("light-test");
        light.TurnOff();

        var notification = new DeviceNotification(
            "security-01",
            "motion_detected",
            new Dictionary<string, object>
            {
                ["zone"] = "hallway"
            });

        light.ReceiveNotification(notification);

        Assert.True(light.IsOn);
        Assert.Equal(70, light.Brightness);
    }
}

This isolation testing is where the mediator pattern really pays off compared to direct device coupling. Each device has a clean ReceiveNotification contract that you can exercise without building the entire smart home graph. Compare this with the state design pattern where testing individual state transitions also benefits from clean separation of concerns.

Adding New Devices Without Changing Existing Code

One of the strongest arguments for the mediator pattern is extensibility. Let's add a smart lock to the system. This device should lock automatically when security arms and unlock when security disarms -- and we won't touch a single line of existing code:

namespace SmartHome.Devices;

public sealed class SmartLock : SmartDeviceBase
{
    public SmartLock(string deviceId)
        : base(deviceId, "Lock")
    {
    }

    public bool IsLocked { get; private set; }

    public void Lock()
    {
        IsLocked = true;

        SendNotification(
            "door_locked",
            new Dictionary<string, object>
            {
                ["locked"] = true
            });
    }

    public void Unlock()
    {
        IsLocked = false;

        SendNotification(
            "door_unlocked",
            new Dictionary<string, object>
            {
                ["locked"] = false
            });
    }

    public override void ReceiveNotification(
        DeviceNotification notification)
    {
        switch (notification.EventType)
        {
            case "security_armed":
                Lock();
                break;

            case "security_disarmed":
                Unlock();
                break;
        }
    }
}

Registration is a single line:

var smartLock = new SmartLock("lock-front-door");
mediator.RegisterDevice(smartLock);

No changes to the mediator. No changes to the security system, lights, thermostat, or music player. The new device plugs into the existing notification infrastructure and participates in the coordination graph immediately. This is the open/closed principle in practice -- the system is open for extension but closed for modification.

Wiring Up With Dependency Injection

For a production application, you'd register the mediator and devices with your DI container. Here's an extension method that wires everything together using IServiceCollection:

using Microsoft.Extensions.DependencyInjection;

using SmartHome.Core;
using SmartHome.Devices;

namespace SmartHome.Configuration;

public static class SmartHomeServiceExtensions
{
    public static IServiceCollection AddSmartHome(
        this IServiceCollection services)
    {
        services.AddSingleton<ISmartHomeMediator>(sp =>
        {
            var mediator =
                new HomeAutomationMediator();

            var devices = sp
                .GetServices<ISmartDevice>();
            foreach (var device in devices)
            {
                mediator.RegisterDevice(device);
            }

            return mediator;
        });

        services
            .AddSingleton<ISmartDevice>(
                new LightController("light-living"));
        services
            .AddSingleton<ISmartDevice>(
                new SmartThermostat("thermo-main"));
        services
            .AddSingleton<ISmartDevice>(
                new SecuritySystem("security-main"));
        services
            .AddSingleton<ISmartDevice>(
                new MusicPlayer("music-main"));

        return services;
    }
}

Adding a new device to the system means adding one more AddSingleton registration. The mediator's factory lambda picks up all ISmartDevice registrations automatically. This approach follows the same dependency injection patterns that make the facade design pattern so easy to compose in .NET applications.

Production Considerations for the Mediator Pattern

The synchronous broadcast mediator works for many scenarios, but production systems often need more. Here are the key areas to address.

Async Notification Handling

If devices perform I/O -- calling external APIs, writing to databases, or sending HTTP requests -- you'll want an async mediator pattern implementation:

public interface IAsyncSmartHomeMediator
{
    void RegisterDevice(IAsyncSmartDevice device);

    Task NotifyAsync(
        IAsyncSmartDevice sender,
        DeviceNotification notification,
        CancellationToken cancellationToken = default);
}

The async version lets devices perform non-blocking operations without stalling the entire notification pipeline. This is particularly relevant when devices interact with cloud services or external hardware controllers.

Error Handling and Resilience

In a broadcast mediator pattern, one device throwing an exception shouldn't prevent other devices from receiving the notification. Wrapping each device's ReceiveNotification call in a try-catch and logging the failure keeps the system resilient:

public void Notify(
    ISmartDevice sender,
    DeviceNotification notification)
{
    foreach (var device in _devices)
    {
        if (device.DeviceId == sender.DeviceId)
        {
            continue;
        }

        try
        {
            device.ReceiveNotification(notification);
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "Device {DeviceId} failed to handle " +
                "{EventType} notification",
                device.DeviceId,
                notification.EventType);
        }
    }
}

Selective Routing

The broadcast approach sends every notification to every device. For larger systems with dozens of devices, you can optimize with selective routing. The mediator pattern is flexible enough to support event subscriptions where devices declare which event types they care about. This is similar in spirit to the strategy design pattern where you swap algorithms at runtime -- here, you'd swap routing strategies based on the event type and target device capabilities.

Logging and Observability

Adding structured logging to the mediator gives you a complete audit trail of all cross-device interactions. Since every notification flows through the mediator, you get a single point where you can log, meter, or trace every coordination event in the system. This is one of the practical benefits the mediator pattern offers over direct device coupling -- centralized observability comes for free.

Mediator Pattern vs. Direct Coupling

Let's summarize the tradeoffs. With direct coupling, the security system from our first code sample had to reference three other devices and change whenever any of them changed. With the mediator pattern, the security system sends a single notification and has no idea what happens next. The mediator pattern trades a web of N-by-N direct dependencies for N-to-1 dependencies through the mediator. As the proxy design pattern wraps access to a single object, and the template method design pattern standardizes algorithmic structure, the mediator pattern standardizes communication structure.

The tradeoff is that the mediator can become a "god object" if you're not careful. Keep your mediator thin -- it should route messages, not contain business logic. If your mediator starts making decisions about what temperature to set or what brightness level to use, you've moved too much responsibility into it. The devices should own their behavior. The mediator should own the routing.

Frequently Asked Questions

When should I use the mediator pattern instead of direct method calls?

Use the mediator pattern when you have multiple objects that need to communicate with each other and the number of direct connections between them is growing. If object A only ever talks to object B, direct method calls are fine. But when object A needs to coordinate with B, C, D, and E -- and they all need to coordinate with each other -- the mediator pattern replaces that mesh of dependencies with a single hub. The smart home system in this article is a clear example: four devices that would otherwise need twelve direct references reduced to four connections through one mediator.

How is the mediator pattern different from the observer pattern?

The observer pattern is a one-to-many broadcast where a subject notifies its subscribers about state changes. The mediator pattern is a many-to-many coordinator where participants communicate through a central object. In the observer pattern, the subject knows it has observers but not what they do. In the mediator pattern, participants don't even know about each other -- they only know the mediator. The mediator can also apply routing logic, filtering, and transformation that the observer pattern doesn't provide.

Does the mediator pattern work with MediatR in .NET?

MediatR is a popular library that implements the mediator pattern for in-process message handling. It uses request/response and notification patterns with handler classes resolved through dependency injection. The concepts in this article -- decoupled communication, central routing, independent handlers -- map directly to how MediatR works. MediatR handles the infrastructure so you can focus on writing handlers, similar to how the HomeAutomationMediator routes notifications to devices.

Can the mediator pattern handle async operations?

Yes. The production considerations section of this article shows an IAsyncSmartHomeMediator interface with a NotifyAsync method. In real applications, mediator pattern implementations frequently need async support for database operations, HTTP calls, or message queue publishing. Libraries like MediatR support async handlers out of the box.

What's the biggest risk when implementing the mediator pattern?

The biggest risk is the mediator becoming a "god object" that accumulates business logic over time. The mediator should only route messages between participants -- it shouldn't decide what the thermostat temperature should be or whether the lights should dim. Keep behavior in the devices and routing in the mediator. If your mediator class is growing beyond simple forwarding logic, refactor the decision-making back into the participant classes.

How do I test individual devices without setting up the full mediator?

Call ReceiveNotification directly on the device with a constructed DeviceNotification. The test class in this article demonstrates this approach -- you create a LightController, send it a "security_armed" notification, and assert that brightness dropped to 10. No mediator, no other devices, no mocking. This isolation testing is one of the mediator pattern's strongest practical advantages.

Should every notification go to every device?

Not necessarily. The broadcast approach in this article is the simplest and works well when you have a manageable number of devices. For larger systems, consider selective routing where devices register interest in specific event types. The mediator pattern is flexible enough to support both approaches. Start with broadcast and optimize to selective routing only when performance or complexity demands it.

Wrapping Up This Mediator Pattern Real-World Example

This implementation shows the mediator pattern solving a real coordination problem -- smart home devices that need to react to each other without direct coupling. We started with a SecuritySystem tangled up with light controllers, thermostats, and music players. We ended with a HomeAutomationMediator that routes notifications between independent devices, each one testable and replaceable on its own.

The mediator pattern works best when you have a group of objects that need many-to-many communication and the direct dependency graph is becoming unmanageable. Smart home automation, chat systems, air traffic control, and UI component coordination are all natural fits. When you find yourself adding more constructor parameters to a class just so it can poke another class when something happens, route that communication through a mediator instead.

Take this smart home example, replace the in-memory devices with real hardware integrations and cloud service calls, add the async and error-handling patterns discussed in the production section, and you've got the foundation for a production-ready home automation coordinator. The mediator pattern keeps your devices decoupled, your coordination logic centralized, and your system ready for the next device you need to plug in.

Factory Method Pattern Real-World Example in C#: Complete Implementation

See Factory Method pattern in action with a complete real-world C# example. Step-by-step implementation of a payment processing system using factory methods.

Builder Pattern Real-World Example in C#: Complete Implementation

See Builder pattern in action with a complete real-world C# example. Step-by-step implementation of a configuration system demonstrating step-by-step object construction.

Mediator Design Pattern in C#: Complete Guide with Examples

Master the mediator design pattern in C# with code examples, real-world scenarios, and best practices for reducing object coupling.

An error has occurred. This application may no longer respond until reloaded. Reload