CrackedRuby CrackedRuby

Overview

The Mediator Pattern defines an object that encapsulates how a set of objects interact. Instead of objects referring to each other directly, they communicate through a mediator. This promotes loose coupling by preventing objects from explicitly referencing one another, allowing their interaction to vary independently.

Gang of Four introduced this pattern in their seminal Design Patterns book as a solution to the problem of tight coupling in systems where many objects interact in complex ways. Without a mediator, objects must maintain references to many other objects, creating a web of dependencies that becomes difficult to modify and understand.

The pattern applies to scenarios where multiple objects communicate in defined but complex ways, and the resulting interdependencies are unstructured and difficult to understand. Air traffic control systems exemplify this pattern: rather than pilots communicating directly with each other to coordinate landings and takeoffs, all communication flows through the control tower, which maintains the coordination logic centrally.

# Without Mediator: Direct coupling
class Airplane
  def initialize
    @other_planes = []
  end
  
  def request_landing
    @other_planes.each do |plane|
      return false if plane.landing?
    end
    land
  end
end

# With Mediator: Centralized coordination
class AirTrafficControl
  def initialize
    @planes = []
  end
  
  def request_landing(plane)
    return false if @planes.any?(&:landing?)
    plane.land
    true
  end
end

The Mediator Pattern introduces a single point of control that reduces the complexity of communication protocols between objects. Rather than each object knowing about N other objects, each object knows only about the mediator, reducing coupling from O(N²) to O(N).

Key Principles

The Mediator Pattern operates on several foundational principles that define its structure and behavior. The pattern consists of four primary components: the Mediator interface, concrete Mediator implementations, the Colleague interface, and concrete Colleague implementations.

Mediator Interface: Defines the communication interface that colleagues use to send and receive messages. This interface abstracts the coordination logic, allowing different mediator implementations to provide different coordination strategies.

Concrete Mediator: Implements the coordination logic and maintains references to all colleague objects. The concrete mediator coordinates colleague behavior by receiving notifications from colleagues and directing which colleagues should respond. This component encapsulates the interaction logic that would otherwise be distributed across multiple colleague objects.

Colleague Interface: Defines the interface for objects that communicate through the mediator. Colleagues maintain a reference to their mediator but remain unaware of other colleague implementations, promoting loose coupling.

Concrete Colleagues: Implement specific behaviors and communicate exclusively through the mediator. When a colleague's state changes or an event occurs, it notifies the mediator rather than interacting with other colleagues directly.

The communication flow follows a specific pattern: a colleague sends a message to the mediator, the mediator processes the message according to its coordination logic, and the mediator forwards appropriate messages to relevant colleagues. This indirection eliminates direct colleague-to-colleague communication.

# Mediator interface
class ChatMediator
  def send_message(message, sender)
    raise NotImplementedError
  end
  
  def add_user(user)
    raise NotImplementedError
  end
end

# Concrete Mediator
class ChatRoom < ChatMediator
  def initialize
    @users = []
  end
  
  def add_user(user)
    @users << user
    user.mediator = self
  end
  
  def send_message(message, sender)
    @users.each do |user|
      user.receive(message) unless user == sender
    end
  end
end

# Colleague interface
class User
  attr_accessor :mediator
  attr_reader :name
  
  def initialize(name)
    @name = name
  end
  
  def send(message)
    @mediator.send_message(message, self)
  end
  
  def receive(message)
    puts "#{@name} received: #{message}"
  end
end

The pattern creates a hub-and-spoke topology where the mediator acts as the hub and colleagues as spokes. This centralization transforms many-to-many relationships into one-to-many relationships, substantially reducing system complexity. The mediator maintains the complete state and logic needed for coordination, while colleagues focus on their individual responsibilities.

Encapsulation of collective behavior represents another key principle. Rather than distributing coordination logic across multiple objects, the mediator consolidates this logic in a single location. This consolidation simplifies modifications to interaction patterns, as changes occur in one place rather than being scattered across multiple colleague classes.

The pattern also enforces the principle of indirect communication. Colleagues never invoke methods on other colleagues directly; all communication passes through the mediator. This indirection provides flexibility to modify interaction patterns without changing colleague implementations.

Ruby Implementation

Ruby's dynamic nature and first-class functions provide several idiomatic approaches to implementing the Mediator Pattern. The most straightforward implementation uses standard class hierarchies, but Ruby's blocks, procs, and modules enable more expressive variations.

Basic Class-Based Implementation

The traditional object-oriented approach defines explicit mediator and colleague classes:

class FormMediator
  def initialize
    @components = {}
  end
  
  def register(name, component)
    @components[name] = component
    component.mediator = self
  end
  
  def notify(sender, event)
    case event
    when :username_changed
      validate_username(sender)
    when :email_changed
      validate_email(sender)
    when :submit
      submit_form if valid?
    end
  end
  
  private
  
  def validate_username(field)
    if field.value.length < 3
      @components[:error].display("Username too short")
      @components[:submit_button].disable
    else
      @components[:error].clear
      @components[:submit_button].enable
    end
  end
  
  def validate_email(field)
    unless field.value.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
      @components[:error].display("Invalid email")
      @components[:submit_button].disable
    end
  end
  
  def valid?
    @components.values.all?(&:valid?)
  end
  
  def submit_form
    puts "Form submitted with values:"
    @components.each { |name, comp| puts "#{name}: #{comp.value}" }
  end
end

class FormField
  attr_accessor :mediator
  attr_reader :value
  
  def initialize(name)
    @name = name
    @value = ""
  end
  
  def change(new_value)
    @value = new_value
    @mediator&.notify(self, :"#{@name}_changed")
  end
  
  def valid?
    !@value.empty?
  end
end

class Button
  attr_accessor :mediator
  
  def initialize
    @enabled = false
  end
  
  def click
    @mediator&.notify(self, :submit) if @enabled
  end
  
  def enable
    @enabled = true
  end
  
  def disable
    @enabled = false
  end
  
  def valid?
    true
  end
  
  def value
    @enabled.to_s
  end
end

Block-Based Coordination

Ruby's blocks enable defining coordination logic inline, reducing the need for separate mediator classes:

class EventMediator
  def initialize(&coordination_block)
    @listeners = Hash.new { |h, k| h[k] = [] }
    @coordination = coordination_block
    instance_eval(&coordination_block) if coordination_block
  end
  
  def on(event, &handler)
    @listeners[event] << handler
  end
  
  def emit(event, data = nil)
    @listeners[event].each { |handler| handler.call(data) }
  end
end

# Usage
mediator = EventMediator.new do
  on(:order_placed) do |order|
    emit(:inventory_update, order.items)
    emit(:notification_send, order.customer)
    emit(:payment_process, order.total)
  end
  
  on(:inventory_update) do |items|
    items.each { |item| puts "Updating inventory for #{item}" }
  end
  
  on(:notification_send) do |customer|
    puts "Sending notification to #{customer}"
  end
end

mediator.emit(:order_placed, OpenStruct.new(
  items: ['Widget', 'Gadget'],
  customer: 'customer@example.com',
  total: 99.99
))

Module-Based Mediation

Ruby modules provide another approach, mixing mediation capabilities into classes:

module Mediates
  def self.included(base)
    base.extend(ClassMethods)
  end
  
  module ClassMethods
    def mediates(*colleague_types)
      colleague_types.each do |type|
        define_method("add_#{type}") do |colleague|
          colleagues_of_type(type) << colleague
          colleague.mediator = self
        end
        
        define_method("#{type}s") do
          colleagues_of_type(type)
        end
      end
    end
  end
  
  def colleagues_of_type(type)
    @colleagues ||= Hash.new { |h, k| h[k] = [] }
    @colleagues[type]
  end
  
  def notify_all(message, exclude: nil)
    @colleagues.values.flatten.each do |colleague|
      colleague.receive(message) unless colleague == exclude
    end
  end
end

class GameMediator
  include Mediates
  mediates :player, :observer
  
  def player_moved(player, position)
    players.each do |p|
      p.update_position(player, position) unless p == player
    end
    
    observers.each do |o|
      o.log_move(player, position)
    end
  end
  
  def game_over(winner)
    notify_all("Game over! Winner: #{winner}")
  end
end

class Player
  attr_accessor :mediator
  attr_reader :name
  
  def initialize(name)
    @name = name
    @position = [0, 0]
  end
  
  def move(x, y)
    @position = [x, y]
    @mediator.player_moved(self, @position)
  end
  
  def update_position(player, position)
    puts "#{@name} sees #{player.name} at #{position}"
  end
  
  def receive(message)
    puts "#{@name}: #{message}"
  end
end

Observable Pattern Integration

Ruby's Observable module from the standard library integrates naturally with mediator implementations:

require 'observer'

class SystemMediator
  include Observable
  
  def initialize
    @components = {}
  end
  
  def register(name, component)
    @components[name] = component
    add_observer(component)
  end
  
  def process_event(event_type, data)
    changed
    notify_observers(event_type, data)
  end
end

class SystemComponent
  def initialize(name)
    @name = name
  end
  
  def update(event_type, data)
    return unless handles?(event_type)
    process(data)
  end
  
  def handles?(event_type)
    respond_to?("handle_#{event_type}")
  end
  
  def process(data)
    event_type = data[:type]
    send("handle_#{event_type}", data) if handles?(event_type)
  end
end

class Logger < SystemComponent
  def handle_error(data)
    puts "[ERROR] #{data[:message]}"
  end
  
  def handle_info(data)
    puts "[INFO] #{data[:message]}"
  end
end

class Alerter < SystemComponent
  def handle_error(data)
    puts "ALERT: Critical error - #{data[:message]}"
  end
end

Thread-Safe Mediator

For concurrent applications, implementing thread-safe mediation requires synchronization:

require 'thread'

class ConcurrentMediator
  def initialize
    @colleagues = []
    @mutex = Mutex.new
    @queue = Queue.new
    start_dispatcher
  end
  
  def register(colleague)
    @mutex.synchronize do
      @colleagues << colleague
      colleague.mediator = self
    end
  end
  
  def send_async(message, sender)
    @queue << { message: message, sender: sender }
  end
  
  def send_sync(message, sender)
    @mutex.synchronize do
      broadcast(message, sender)
    end
  end
  
  private
  
  def start_dispatcher
    @dispatcher = Thread.new do
      loop do
        msg_data = @queue.pop
        @mutex.synchronize do
          broadcast(msg_data[:message], msg_data[:sender])
        end
      end
    end
  end
  
  def broadcast(message, sender)
    @colleagues.each do |colleague|
      colleague.receive(message) unless colleague == sender
    end
  end
end

Practical Examples

GUI Component Coordination

Dialog boxes with interdependent components demonstrate practical mediator usage. Form validation, button enabling, and error display require coordination that the mediator centralizes:

class DialogMediator
  def initialize
    @fields = {}
    @validation_rules = {}
  end
  
  def register_field(name, field, &validation)
    @fields[name] = field
    @validation_rules[name] = validation
    field.mediator = self
  end
  
  def field_changed(field, value)
    validate_all
    update_dependencies(field, value)
  end
  
  def submit_requested
    if all_valid?
      data = collect_data
      puts "Submitting: #{data}"
      true
    else
      display_errors
      false
    end
  end
  
  private
  
  def validate_all
    @errors = {}
    @fields.each do |name, field|
      validation = @validation_rules[name]
      error = validation&.call(field.value)
      @errors[name] = error if error
    end
  end
  
  def all_valid?
    @errors.empty?
  end
  
  def update_dependencies(changed_field, value)
    case changed_field
    when @fields[:country]
      update_state_options(value)
    when @fields[:membership_type]
      update_pricing(value)
    end
  end
  
  def update_state_options(country)
    states = country == 'US' ? ['CA', 'NY', 'TX'] : []
    @fields[:state]&.set_options(states)
  end
  
  def update_pricing(membership)
    price = membership == 'premium' ? 99 : 49
    @fields[:price_display]&.set_text("$#{price}/month")
  end
  
  def collect_data
    @fields.transform_values(&:value)
  end
  
  def display_errors
    @errors.each do |field, error|
      puts "#{field}: #{error}"
    end
  end
end

class TextField
  attr_accessor :mediator
  attr_reader :value
  
  def initialize
    @value = ""
  end
  
  def set_value(new_value)
    @value = new_value
    @mediator&.field_changed(self, @value)
  end
end

Chat Room System

Multi-user chat rooms require coordinating message delivery, user presence, and permission management:

class ChatRoomMediator
  def initialize(room_name)
    @room_name = room_name
    @users = {}
    @message_history = []
    @moderators = Set.new
  end
  
  def join(user)
    @users[user.id] = user
    user.mediator = self
    
    send_history(user)
    broadcast_system_message("#{user.name} joined the room", exclude: user)
  end
  
  def leave(user)
    @users.delete(user.id)
    broadcast_system_message("#{user.name} left the room")
  end
  
  def send_message(sender, content)
    return kick(sender, "Profanity not allowed") if contains_profanity?(content)
    
    message = {
      sender: sender.name,
      content: content,
      timestamp: Time.now
    }
    
    @message_history << message
    @users.values.each { |user| user.receive_message(message) }
  end
  
  def send_private_message(sender, recipient_id, content)
    recipient = @users[recipient_id]
    return unless recipient
    
    message = {
      sender: sender.name,
      content: content,
      timestamp: Time.now,
      private: true
    }
    
    recipient.receive_message(message)
    sender.receive_message(message.merge(sent: true))
  end
  
  def promote_to_moderator(user)
    @moderators << user.id
    send_system_message(user, "You are now a moderator")
  end
  
  def kick(target, reason)
    return unless moderator?(target.id) == false
    
    send_system_message(target, "Kicked: #{reason}")
    leave(target)
  end
  
  private
  
  def moderator?(user_id)
    @moderators.include?(user_id)
  end
  
  def contains_profanity?(content)
    content.match?(/badword1|badword2/)
  end
  
  def send_history(user)
    @message_history.last(50).each { |msg| user.receive_message(msg) }
  end
  
  def broadcast_system_message(content, exclude: nil)
    message = { system: true, content: content, timestamp: Time.now }
    @users.values.each do |user|
      user.receive_message(message) unless user == exclude
    end
  end
  
  def send_system_message(user, content)
    user.receive_message({ system: true, content: content, timestamp: Time.now })
  end
end

class ChatUser
  attr_accessor :mediator
  attr_reader :id, :name
  
  def initialize(id, name)
    @id = id
    @name = name
  end
  
  def send(content)
    @mediator.send_message(self, content)
  end
  
  def send_private(recipient_id, content)
    @mediator.send_private_message(self, recipient_id, content)
  end
  
  def receive_message(message)
    if message[:system]
      puts "[SYSTEM] #{message[:content]}"
    elsif message[:private]
      prefix = message[:sent] ? "To" : "From"
      puts "[#{prefix} #{message[:sender]}] #{message[:content]}"
    else
      puts "[#{message[:sender]}] #{message[:content]}"
    end
  end
end

Stock Trading System

Financial trading systems coordinate multiple services—order matching, risk management, notification, and audit logging:

class TradingMediator
  def initialize
    @order_book = OrderBook.new
    @risk_manager = RiskManager.new
    @notifier = NotificationService.new
    @auditor = AuditLogger.new
    
    [@order_book, @risk_manager, @notifier, @auditor].each do |service|
      service.mediator = self
    end
  end
  
  def place_order(order)
    @auditor.log(:order_received, order)
    
    unless @risk_manager.approve?(order)
      @notifier.send(order.trader, "Order rejected: Risk limit exceeded")
      @auditor.log(:order_rejected, order, reason: :risk)
      return false
    end
    
    execution = @order_book.match(order)
    
    if execution[:status] == :filled
      @auditor.log(:order_filled, order, execution_price: execution[:price])
      @notifier.send(order.trader, "Order filled at #{execution[:price]}")
      @risk_manager.update_exposure(order.trader, execution)
      true
    else
      @auditor.log(:order_partial, order, filled: execution[:filled])
      @notifier.send(order.trader, "Order partially filled")
      false
    end
  end
  
  def cancel_order(order_id, trader)
    removed = @order_book.remove(order_id, trader)
    
    if removed
      @auditor.log(:order_cancelled, order_id: order_id, trader: trader)
      @notifier.send(trader, "Order cancelled")
      @risk_manager.release_exposure(trader, removed)
      true
    else
      false
    end
  end
  
  def market_data_update(symbol, price)
    @risk_manager.update_positions(symbol, price)
    @notifier.broadcast_price(symbol, price)
  end
end

class TradingService
  attr_accessor :mediator
end

class OrderBook < TradingService
  def initialize
    @orders = {}
  end
  
  def match(order)
    # Simplified matching logic
    { status: :filled, price: order.limit_price, filled: order.quantity }
  end
  
  def remove(order_id, trader)
    @orders.delete(order_id)
  end
end

class RiskManager < TradingService
  def initialize
    @exposure = Hash.new(0)
    @limits = Hash.new(100_000)
  end
  
  def approve?(order)
    potential_exposure = @exposure[order.trader] + order.value
    potential_exposure <= @limits[order.trader]
  end
  
  def update_exposure(trader, execution)
    @exposure[trader] += execution[:price] * execution[:filled]
  end
  
  def release_exposure(trader, order)
    @exposure[trader] -= order.value
  end
  
  def update_positions(symbol, price)
    # Mark-to-market updates
  end
end

Design Considerations

The Mediator Pattern suits specific scenarios but introduces its own complexities. Understanding when to apply the pattern and when to avoid it requires analyzing the nature of object interactions and system evolution patterns.

When to Use the Mediator Pattern

Apply the mediator when object interactions form complex, hard-to-understand webs of dependencies. Systems where each object references multiple other objects create quadratic coupling—each new object potentially requires updates to all existing objects. The mediator linearizes this coupling, making the system easier to understand and modify.

Use the pattern when interaction logic changes frequently. Centralizing coordination logic in the mediator enables modifying interaction patterns without touching individual colleague classes. This separation particularly benefits systems where business rules governing object interactions evolve independently from the objects themselves.

Consider the mediator for systems requiring dynamic interaction patterns. When objects need to coordinate differently based on runtime state or configuration, the mediator provides a central point for implementing conditional coordination logic. Dialog boxes demonstrate this: field interdependencies vary based on user selections, and the mediator adjusts coordination accordingly.

The pattern excels in scenarios requiring cross-cutting concerns in object interactions. Logging, validation, and auditing apply to multiple interaction types. Rather than duplicating this logic across colleague classes, the mediator implements it once and applies it uniformly.

Trade-offs

The mediator centralizes complexity but doesn't eliminate it. The mediator object itself can become complex, containing intricate coordination logic. This concentration creates a "god object" risk—a single class responsible for too many concerns. Monitoring mediator complexity remains critical; mediators exceeding several hundred lines suggest splitting into multiple mediators or reconsidering the design.

The pattern introduces a single point of failure. All colleague communication flows through the mediator, making it critical to system operation. Bugs in the mediator affect all interactions, and mediator unavailability prevents any colleague communication. This risk necessitates robust testing and error handling in mediator implementations.

Performance implications emerge in high-throughput systems. Every colleague interaction involves mediator overhead—method calls, coordination logic, and potential data transformation. For latency-sensitive applications, this indirection may prove too expensive. Direct colleague communication, while increasing coupling, eliminates mediator overhead.

The mediator creates implicit coupling between colleagues and coordination logic. While colleagues avoid direct references to each other, they depend on the mediator's coordination behavior. Changing mediator logic affects all colleagues, creating a different form of coupling. This coupling proves more manageable than colleague-to-colleague coupling but still requires careful management.

Alternatives

The Observer pattern provides an alternative for one-way, broadcast communication. When objects need to notify others of state changes without requiring complex bidirectional coordination, observers decouple publishers from subscribers without a central mediator. The observer pattern suits event-driven systems where multiple objects react to state changes.

The Command pattern works when the coordination involves primarily request queuing and execution rather than complex bidirectional communication. Commands encapsulate requests as objects, enabling queuing, logging, and undo functionality without a mediator.

Service layers in application architecture serve mediator-like roles at higher abstraction levels. Rather than coordinating object interactions, service layers coordinate subsystem interactions. This approach scales better for large systems but operates at coarser granularity than the mediator pattern.

Event buses provide infrastructure-level mediation. Rather than custom mediator objects, event buses offer publish-subscribe mechanisms that decouple components through events. This approach works well for loosely coupled, scalable systems but adds infrastructure complexity.

Decision Criteria

Choose the mediator when colleague-to-colleague coupling exceeds mediator-to-colleague coupling in complexity. If coordinating N objects directly requires O(N²) relationships but a mediator requires O(N) relationships with simpler logic, the mediator wins.

Favor direct communication when interactions remain simple and stable. Two objects exchanging data without complex coordination logic don't benefit from mediation. The pattern overhead exceeds its benefits for straightforward relationships.

Consider the rate of change for interaction logic versus object logic. When interaction patterns change more frequently than object implementations, the mediator provides value by isolating interaction logic. When objects change frequently but interactions remain stable, the mediator adds unnecessary indirection.

Evaluate testability requirements. Mediators enable testing coordination logic independently from colleague implementations through mocking. Systems requiring extensive interaction testing benefit from this separation. Simple systems with minimal coordination logic don't justify the additional testing complexity mediators introduce.

Common Patterns

Several variations and extensions of the Mediator Pattern address specific scenarios and integrate with other design patterns to create comprehensive solutions.

Hierarchical Mediators

Complex systems require multiple mediators organized hierarchically. Rather than one massive mediator coordinating everything, hierarchical mediators delegate responsibilities:

class ApplicationMediator
  def initialize
    @subsystem_mediators = {}
  end
  
  def register_subsystem(name, mediator)
    @subsystem_mediators[name] = mediator
    mediator.parent_mediator = self
  end
  
  def route_message(source_subsystem, target_subsystem, message)
    target = @subsystem_mediators[target_subsystem]
    target&.receive_from_parent(source_subsystem, message)
  end
end

class SubsystemMediator
  attr_accessor :parent_mediator
  
  def initialize(name)
    @name = name
    @components = []
  end
  
  def notify(component, message)
    if message[:scope] == :local
      handle_locally(component, message)
    else
      @parent_mediator.route_message(@name, message[:target], message)
    end
  end
  
  def receive_from_parent(source, message)
    @components.each { |c| c.receive_external(source, message) }
  end
  
  private
  
  def handle_locally(component, message)
    @components.each do |c|
      c.receive_internal(message) unless c == component
    end
  end
end

Event-Driven Mediators

Event-driven architectures integrate naturally with mediators. Rather than direct method calls, colleagues publish events and the mediator subscribes and coordinates:

class EventDrivenMediator
  def initialize
    @subscribers = Hash.new { |h, k| h[k] = [] }
    @event_queue = []
  end
  
  def subscribe(event_type, handler)
    @subscribers[event_type] << handler
  end
  
  def publish(event)
    @event_queue << event
    process_queue
  end
  
  def process_queue
    until @event_queue.empty?
      event = @event_queue.shift
      @subscribers[event.type].each do |handler|
        result = handler.call(event)
        @event_queue << result if result.is_a?(Event)
      end
    end
  end
end

class Event
  attr_reader :type, :data, :timestamp
  
  def initialize(type, data = {})
    @type = type
    @data = data
    @timestamp = Time.now
  end
end

# Usage
mediator = EventDrivenMediator.new

mediator.subscribe(:user_registered) do |event|
  puts "Sending welcome email to #{event.data[:email]}"
  Event.new(:email_sent, recipient: event.data[:email])
end

mediator.subscribe(:email_sent) do |event|
  puts "Logging email delivery to #{event.data[:recipient]}"
  nil
end

mediator.publish(Event.new(:user_registered, email: 'user@example.com'))

State-Based Mediation

Mediators maintaining state enable context-dependent coordination. The mediator's current state determines how it handles colleague messages:

class StatefulMediator
  def initialize
    @state = :idle
    @context = {}
  end
  
  def notify(sender, message)
    case @state
    when :idle
      handle_idle_message(sender, message)
    when :processing
      queue_message(sender, message)
    when :error
      reject_message(sender, message)
    end
  end
  
  private
  
  def handle_idle_message(sender, message)
    @state = :processing
    @context[:current_operation] = message[:operation]
    
    process_message(sender, message)
    
    @state = :idle
    process_queued_messages
  end
  
  def queue_message(sender, message)
    @message_queue ||= []
    @message_queue << [sender, message]
  end
end

Registry-Based Mediation

For systems with dynamic colleague registration, registry-based mediators track available components and route messages accordingly:

class RegistryMediator
  def initialize
    @registry = {}
    @capabilities = Hash.new { |h, k| h[k] = [] }
  end
  
  def register(component, capabilities: [])
    id = generate_id
    @registry[id] = component
    
    capabilities.each do |capability|
      @capabilities[capability] << id
    end
    
    component.mediator = self
    component.id = id
    
    id
  end
  
  def unregister(component_id)
    @registry.delete(component_id)
    @capabilities.each { |_, ids| ids.delete(component_id) }
  end
  
  def request(capability, message)
    component_ids = @capabilities[capability]
    return nil if component_ids.empty?
    
    component_id = select_component(component_ids, capability)
    @registry[component_id].handle(message)
  end
  
  def broadcast(capability, message)
    @capabilities[capability].each do |id|
      @registry[id].handle(message)
    end
  end
  
  private
  
  def select_component(component_ids, capability)
    # Load balancing, round-robin, or other selection strategy
    component_ids.sample
  end
  
  def generate_id
    SecureRandom.uuid
  end
end

Mediator with Validation Chain

Complex coordination often requires validation chains where multiple checks must pass before action occurs:

class ValidatingMediator
  def initialize
    @validators = []
    @handlers = []
  end
  
  def add_validator(&block)
    @validators << block
  end
  
  def add_handler(&block)
    @handlers << block
  end
  
  def process(sender, message)
    @validators.each do |validator|
      result = validator.call(sender, message)
      return { success: false, error: result } unless result == true
    end
    
    responses = @handlers.map { |handler| handler.call(sender, message) }
    { success: true, responses: responses }
  end
end

# Usage
mediator = ValidatingMediator.new

mediator.add_validator do |sender, message|
  message[:content].length <= 1000 || "Message too long"
end

mediator.add_validator do |sender, message|
  sender.permissions.include?(:post) || "Insufficient permissions"
end

mediator.add_handler do |sender, message|
  Database.save(message)
end

mediator.add_handler do |sender, message|
  NotificationService.notify(message[:recipients])
end

Common Pitfalls

Implementing the Mediator Pattern incorrectly leads to maintenance problems and architectural issues that negate the pattern's benefits.

Monolithic Mediator

The most common mistake involves creating mediators that handle too many responsibilities. As systems grow, developers add coordination logic to existing mediators rather than creating new ones, resulting in thousand-line mediators that become maintenance nightmares:

# Anti-pattern: Monolithic mediator
class SystemMediator
  def notify(sender, event)
    case event[:type]
    when :user_action then handle_user_action(event)
    when :database_event then handle_database_event(event)
    when :network_event then handle_network_event(event)
    when :ui_event then handle_ui_event(event)
    when :file_event then handle_file_event(event)
    # ... hundreds more event types
    end
  end
  
  # ... hundreds of private methods
end

# Better: Specialized mediators
class UserActionMediator
  # Handles only user-related coordination
end

class DataAccessMediator
  # Handles only database-related coordination
end

Mediator size should remain proportional to the complexity of coordination it manages. Mediators exceeding 200-300 lines warrant examination for splitting opportunities. Creating multiple mediators organized by domain or subsystem maintains the benefits of centralized coordination without monolithic complexity.

Colleague Knowledge of Mediator Details

Colleagues should remain agnostic about mediator implementation details. When colleagues know too much about mediator structure or behavior, the coupling supposedly eliminated by the pattern reappears in a different form:

# Anti-pattern: Colleague knows mediator details
class BadColleague
  def perform_action
    if @mediator.is_a?(ChatMediator)
      @mediator.send_message(self, @message)
    elsif @mediator.is_a?(NotificationMediator)
      @mediator.notify_users(self, @message)
    end
  end
end

# Better: Uniform interface
class GoodColleague
  def perform_action
    @mediator.notify(self, type: :message, content: @message)
  end
end

Colleagues should interact with mediators through abstract interfaces or protocols. Type-checking mediator implementations or calling mediator-specific methods indicates poor abstraction. The mediator interface should remain stable even as mediator implementations change.

Insufficient Encapsulation

Mediators must encapsulate coordination logic completely. Leaking coordination logic into colleagues defeats the pattern's purpose:

# Anti-pattern: Coordination logic in colleague
class Component
  def update_value(value)
    @value = value
    
    # Should be in mediator
    if value > 100
      @mediator.other_components.each do |comp|
        comp.set_warning(true)
      end
    end
  end
end

# Better: Pure delegation
class Component
  def update_value(value)
    @value = value
    @mediator.notify(self, event: :value_changed, value: value)
  end
end

class Mediator
  def notify(sender, event:, value:)
    if event == :value_changed && value > 100
      warn_other_components(sender)
    end
  end
end

Colleagues should focus exclusively on their individual responsibilities. Any logic determining how components interact belongs in the mediator. Coordination logic appearing in colleague classes suggests incomplete mediation.

Bypassing the Mediator

Systems sometimes evolve to allow direct colleague-to-colleague communication, undermining the pattern's benefits. This occurs gradually as developers add "just this one" direct reference for perceived efficiency:

# Anti-pattern: Direct colleague reference
class UserInterface
  def initialize(mediator, data_store)
    @mediator = mediator
    @data_store = data_store  # Direct reference
  end
  
  def save
    @data_store.save(@data)  # Bypasses mediator
  end
end

# Better: All communication through mediator
class UserInterface
  def initialize(mediator)
    @mediator = mediator
  end
  
  def save
    @mediator.notify(self, event: :save_requested, data: @data)
  end
end

Once colleagues maintain direct references, the coupling returns. Maintaining discipline to route all communication through the mediator preserves architectural integrity. Performance optimization should target mediator implementation, not bypass the mediator.

Missing Mediator Abstraction

Concrete mediator classes without abstract interfaces limit flexibility and testability:

# Problem: Concrete mediator, no interface
class ConcreteMediator
  def coordinate(sender, message)
    # Implementation
  end
end

class Component
  def initialize(mediator)
    @mediator = mediator  # Depends on concrete class
  end
end

# Better: Abstract interface
class Mediator
  def coordinate(sender, message)
    raise NotImplementedError
  end
end

class ChatMediator < Mediator
  def coordinate(sender, message)
    # Implementation
  end
end

class MockMediator < Mediator
  def coordinate(sender, message)
    # Test implementation
  end
end

Abstract mediator interfaces enable testing colleagues with mock mediators and swapping mediator implementations without modifying colleagues. This abstraction proves essential for unit testing and system evolution.

Synchronous Blocking

Mediators processing messages synchronously can create performance bottlenecks and deadlocks in concurrent systems:

# Problem: Synchronous processing blocks
class SyncMediator
  def notify(sender, message)
    colleagues.each do |colleague|
      colleague.handle(message)  # Blocks if colleague is slow
    end
  end
end

# Better: Asynchronous processing
class AsyncMediator
  def initialize
    @queue = Queue.new
    @worker = Thread.new { process_queue }
  end
  
  def notify(sender, message)
    @queue << [sender, message]
  end
  
  private
  
  def process_queue
    loop do
      sender, message = @queue.pop
      colleagues.each { |c| c.handle(message) }
    end
  end
end

For systems with high message throughput or slow colleague operations, asynchronous mediation prevents blocking. Queue-based implementations enable continued operation even when individual colleagues lag.

Reference

Core Components

Component Responsibility Key Methods
Mediator Defines coordination interface notify, register, coordinate
ConcreteMediator Implements coordination logic Process colleague notifications, manage colleague references
Colleague Defines colleague interface send, receive, notify mediator
ConcreteColleague Implements specific behavior Business logic, mediator notification

Communication Patterns

Pattern Description Use Case
Broadcast Mediator sends to all colleagues System announcements, state synchronization
Unicast Mediator sends to specific colleague Directed messages, command execution
Filtered Mediator sends based on criteria Selective notification, permission-based routing
Queued Messages queued for processing Async systems, load management
Priority Messages processed by priority Critical events, SLA enforcement

Implementation Strategies

Strategy Advantages Disadvantages
Class-based Clear structure, type safety More boilerplate, less flexible
Block-based Concise, flexible Less explicit structure
Module-based Reusable, composable Additional complexity
Observer integration Familiar pattern One-way communication focus
Event-driven Decoupled, scalable Harder to trace execution flow
Registry-based Dynamic components Runtime overhead

When to Use Mediator

Scenario Use Mediator Direct Communication
Complex interdependencies Yes No
Simple two-way communication No Yes
Frequently changing interactions Yes No
Stable relationships No Yes
Need centralized control Yes No
Performance critical path Maybe Likely
Dynamic component composition Yes No
Fixed small component set No Yes

Common Message Types

Type Purpose Data Included
Notification Inform of state change sender, event type, new state
Request Ask for action sender, action type, parameters
Response Reply to request status, result, error info
Query Ask for information query type, criteria
Command Direct action command type, target, params
Event Report occurrence event type, timestamp, context

Testing Strategies

Aspect Approach
Mediator isolation Mock colleagues, test coordination logic independently
Colleague isolation Mock mediator, test colleague behavior
Integration Real components, verify end-to-end coordination
Message flow Trace message routing, verify delivery
Error handling Inject failures, verify recovery behavior
Concurrency Simulate concurrent access, verify thread safety
Performance Measure throughput, latency under load

Ruby-Specific Patterns

Pattern Implementation
Block-based mediator EventMediator.new { on(:event) { handler } }
Module mixing include Mediates; mediates :colleague_type
Observable integration require 'observer'; include Observable
Thread-safe queue Queue.new for async message processing
Dynamic registration method_missing for flexible colleague addition
Proc handlers Store handlers as procs/lambdas

Mediator Size Guidelines

Lines of Code Assessment Action
0-100 Appropriate Continue
100-200 Monitor Watch for growth
200-300 Review Consider splitting
300+ Too large Split into multiple mediators

Anti-Pattern Detection

Symptom Anti-Pattern Solution
Thousand-line mediator Monolithic mediator Split by domain
Type checking mediator Insufficient abstraction Define abstract interface
Logic in colleagues Leaking coordination Move to mediator
Direct colleague references Bypassing mediator Remove references
Blocking operations Synchronous bottleneck Implement async queue
Complex nested conditions Poor organization Extract coordination methods