HardТеория4 min

Mediator

Паттерн Mediator в Go: централизованная координация, чат-системы и event bus

Mediator (Посредник)

Проблема

Множество объектов взаимодействуют друг с другом напрямую, создавая сеть зависимостей ("каждый знает каждого"). Mediator централизует коммуникацию -- объекты общаются только через посредника.

Диаграмма

    Без Mediator:                С Mediator:
    A <--> B                     A --> Mediator <-- B
    A <--> C                     C --> Mediator <-- D
    B <--> D                     (все через центр)
    C <--> D
    (6 связей)                   (4 связи)

Реализация: Чат-комната

package chat

import (
    "fmt"
    "strings"
    "time"
)

// Message represents a chat message.
type Message struct {
    From      string
    To        string // empty = broadcast
    Content   string
    Timestamp time.Time
}

// Participant can send and receive messages.
type Participant interface {
    Name() string
    Receive(msg Message)
}

// Mediator coordinates communication between participants.
type Mediator interface {
    Register(p Participant)
    Send(msg Message)
}

// --- Chat room (concrete mediator) ---

type chatRoom struct {
    name         string
    participants map[string]Participant
    history      []Message
}

// NewChatRoom creates a chat room mediator.
func NewChatRoom(name string) Mediator {
    return &chatRoom{
        name:         name,
        participants: make(map[string]Participant),
    }
}

func (r *chatRoom) Register(p Participant) {
    r.participants[p.Name()] = p
    r.Send(Message{
        From:      "system",
        Content:   fmt.Sprintf("%s joined the room", p.Name()),
        Timestamp: time.Now(),
    })
}

func (r *chatRoom) Send(msg Message) {
    msg.Timestamp = time.Now()
    r.history = append(r.history, msg)

    if msg.To != "" {
        // Direct message
        if p, ok := r.participants[msg.To]; ok {
            p.Receive(msg)
        }
        return
    }

    // Broadcast to all except sender
    for name, p := range r.participants {
        if name != msg.From {
            p.Receive(msg)
        }
    }
}

// --- User (concrete participant) ---

type User struct {
    name     string
    room     Mediator
    messages []Message
}

func NewUser(name string, room Mediator) *User {
    u := &User{name: name, room: room}
    room.Register(u)
    return u
}

func (u *User) Name() string { return u.name }

func (u *User) Receive(msg Message) {
    u.messages = append(u.messages, msg)
    fmt.Printf("[%s] %s: %s\n", u.name, msg.From, msg.Content)
}

func (u *User) Say(content string) {
    u.room.Send(Message{From: u.name, Content: content})
}

func (u *User) DM(to, content string) {
    u.room.Send(Message{From: u.name, To: to, Content: content})
}

Использование

func main() {
    room := chat.NewChatRoom("Go Developers")

    alice := chat.NewUser("Alice", room)
    bob := chat.NewUser("Bob", room)
    charlie := chat.NewUser("Charlie", room)

    alice.Say("Hey everyone!")
    bob.Say("Hi Alice!")
    charlie.DM("Alice", "Can we talk privately?")
}

// Output:
// [Alice] system: Bob joined the room
// [Alice] system: Charlie joined the room
// [Bob] system: Charlie joined the room
// [Bob] Alice: Hey everyone!
// [Charlie] Alice: Hey everyone!
// [Alice] Bob: Hi Alice!
// [Charlie] Bob: Hi Alice!
// [Alice] Charlie: Can we talk privately?

Реальный пример: Event Bus

package event

import (
    "context"
    "log/slog"
    "sync"
)

// Event represents a domain event.
type Event struct {
    Type    string
    Payload any
}

// Handler processes an event.
type Handler func(ctx context.Context, evt Event) error

// Bus is the mediator that routes events to handlers.
type Bus struct {
    mu       sync.RWMutex
    handlers map[string][]Handler
    logger   *slog.Logger
}

// NewBus creates an event bus mediator.
func NewBus(logger *slog.Logger) *Bus {
    return &Bus{
        handlers: make(map[string][]Handler),
        logger:   logger,
    }
}

// Subscribe registers a handler for an event type.
func (b *Bus) Subscribe(eventType string, handler Handler) {
    b.mu.Lock()
    defer b.mu.Unlock()
    b.handlers[eventType] = append(b.handlers[eventType], handler)
}

// Publish sends an event to all subscribers.
func (b *Bus) Publish(ctx context.Context, evt Event) {
    b.mu.RLock()
    handlers := b.handlers[evt.Type]
    b.mu.RUnlock()

    for _, h := range handlers {
        if err := h(ctx, evt); err != nil {
            b.logger.ErrorContext(ctx, "event handler failed",
                "type", evt.Type,
                "error", err,
            )
        }
    }
}

// PublishAsync sends events to handlers concurrently.
func (b *Bus) PublishAsync(ctx context.Context, evt Event) {
    b.mu.RLock()
    handlers := b.handlers[evt.Type]
    b.mu.RUnlock()

    var wg sync.WaitGroup
    for _, h := range handlers {
        wg.Add(1)
        go func() {
            defer wg.Done()
            if err := h(ctx, evt); err != nil {
                b.logger.ErrorContext(ctx, "event handler failed",
                    "type", evt.Type,
                    "error", err,
                )
            }
        }()
    }
    wg.Wait()
}

Использование:

func main() {
    bus := event.NewBus(slog.Default())

    // Subscribe different services to events
    bus.Subscribe("order.created", func(ctx context.Context, evt event.Event) error {
        fmt.Println("Inventory: reserving stock for order")
        return nil
    })

    bus.Subscribe("order.created", func(ctx context.Context, evt event.Event) error {
        fmt.Println("Email: sending confirmation")
        return nil
    })

    bus.Subscribe("order.created", func(ctx context.Context, evt event.Event) error {
        fmt.Println("Analytics: tracking order")
        return nil
    })

    // Publish event -- mediator routes to all subscribers
    bus.Publish(context.Background(), event.Event{
        Type:    "order.created",
        Payload: map[string]any{"order_id": "ORD-123"},
    })
}

Когда использовать

Используйте, когда:

  • Множество объектов общаются друг с другом (N-to-N)
  • Нужно уменьшить связанность между компонентами
  • Логика координации сложна и меняется
  • Event-driven архитектура

Не используйте, когда:

  • Два компонента (достаточно прямой связи)
  • Mediator становится "God Object" с тысячами строк
  • Простая линейная обработка (достаточно Chain of Responsibility)

Сравнение с похожими паттернами

Паттерн Отличие
Observer Одностороннее уведомление; Mediator -- двустороннее взаимодействие
Facade Упрощает подсистему; Mediator координирует равноправных участников
Chain of Responsibility Последовательная передача; Mediator -- централизованная координация

Проверь себя

Какую проблему решает паттерн Mediator?

Чем Mediator отличается от Observer?