Observer Pattern
Observer Pattern
Section titled “Observer Pattern”The Observer pattern defines a one-to-many dependency between objects. When one object (the subject/publisher) changes state, all its dependents (observers/subscribers) are notified automatically.
When to Use
Section titled “When to Use”- Event systems (UI events, domain events)
- Real-time notifications (stock prices, live scores)
- Decoupling components that need to react to state changes
- Implementing event-driven architectures
C# Implementation
Section titled “C# Implementation”// Event interfacepublic interface IOrderEventHandler{ Task HandleAsync(OrderPlacedEvent evt);}
// Subject (publisher)public class OrderService{ private readonly List<IOrderEventHandler> _handlers = new();
public void Subscribe(IOrderEventHandler handler) => _handlers.Add(handler); public void Unsubscribe(IOrderEventHandler handler) => _handlers.Remove(handler);
public async Task PlaceOrderAsync(CreateOrderRequest request) { // Create and save order... var order = new Order(request); await _orderRepository.SaveAsync(order);
// Notify all subscribers var evt = new OrderPlacedEvent(order.Id, order.CustomerId, order.Total); foreach (var handler in _handlers) { await handler.HandleAsync(evt); } }}
// Observerspublic class EmailNotificationHandler : IOrderEventHandler{ public async Task HandleAsync(OrderPlacedEvent evt) { await _emailService.SendOrderConfirmationAsync(evt.CustomerId, evt.OrderId); }}
public class InventoryHandler : IOrderEventHandler{ public async Task HandleAsync(OrderPlacedEvent evt) { await _inventoryService.ReserveItemsAsync(evt.OrderId); }}
// Setupvar orderService = new OrderService();orderService.Subscribe(new EmailNotificationHandler());orderService.Subscribe(new InventoryHandler());C# Events (Built-in Observer)
Section titled “C# Events (Built-in Observer)”C# has built-in event support using event and delegates:
public class StockPrice{ public string Symbol { get; } public decimal Price { get; private set; }
public event EventHandler<PriceChangedEventArgs>? PriceChanged;
public StockPrice(string symbol, decimal initialPrice) { Symbol = symbol; Price = initialPrice; }
public void UpdatePrice(decimal newPrice) { var old = Price; Price = newPrice; PriceChanged?.Invoke(this, new PriceChangedEventArgs(Symbol, old, newPrice)); }}
// Subscribevar aapl = new StockPrice("AAPL", 175.00m);
aapl.PriceChanged += (sender, args) => Console.WriteLine($"{args.Symbol}: {args.OldPrice} → {args.NewPrice}");
aapl.UpdatePrice(178.50m); // triggers notificationTypeScript — Event Emitter
Section titled “TypeScript — Event Emitter”type EventMap = { 'user:registered': { userId: string; email: string }; 'order:placed': { orderId: string; amount: number }; 'payment:failed': { orderId: string; reason: string };};
class EventBus { private listeners = new Map<string, Function[]>();
on<K extends keyof EventMap>(event: K, listener: (payload: EventMap[K]) => void): void { const list = this.listeners.get(event) ?? []; list.push(listener); this.listeners.set(event, list); }
off<K extends keyof EventMap>(event: K, listener: Function): void { const list = this.listeners.get(event) ?? []; this.listeners.set(event, list.filter(l => l !== listener)); }
emit<K extends keyof EventMap>(event: K, payload: EventMap[K]): void { this.listeners.get(event)?.forEach(listener => listener(payload)); }}
// Usageconst bus = new EventBus();
bus.on('user:registered', ({ userId, email }) => { console.log(`Send welcome email to ${email}`);});
bus.on('user:registered', ({ userId }) => { console.log(`Create default preferences for user ${userId}`);});
bus.emit('user:registered', { userId: '123', email: 'alice@example.com' });Observer in React
Section titled “Observer in React”React’s useEffect with state is the Observer pattern built-in:
// Component observes external data (subscribes to changes)function StockTicker({ symbol }: { symbol: string }) { const [price, setPrice] = useState<number | null>(null);
useEffect(() => { const subscription = stockFeed.subscribe(symbol, setPrice); return () => subscription.unsubscribe(); // cleanup = unsubscribe }, [symbol]);
return <div>{symbol}: ${price?.toFixed(2) ?? 'Loading...'}</div>;}Domain Events
Section titled “Domain Events”In Domain-Driven Design, domain events are a core use of the Observer pattern:
// Raise events in domain entitiespublic class Order{ private readonly List<IDomainEvent> _events = new(); public IReadOnlyList<IDomainEvent> Events => _events;
public void Place() { Status = OrderStatus.Placed; _events.Add(new OrderPlacedEvent(Id, CustomerId)); }}
// Dispatch in the repository or unit of workawait orderRepository.SaveAsync(order);foreach (var evt in order.Events) await _mediator.PublishAsync(evt);