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 -- централизованная координация |