Publisher methods the agent must trace through to know what side effects occur. Adding a new consumer requires editing the publisher; per-edit context cost grows with consumer count; tests for the publisher load every consumer's mock.
The publisher is short and consumer-agnostic; the agent reads it once and trusts it. Each consumer is one file with one event-handling concern; the agent verifies each consumer in isolation.
Before the refactoring
// Cart knows which collaborators to notify and how.class Cart {constructor(inventory, analytics, shipping) {this.items = [];this.inventory = inventory;this.analytics = analytics;this.shipping = shipping;}addItem(item, quantity) {this.items.push({ item, quantity });this.inventory.reserve(item, quantity);this.analytics.recordAdd(item, quantity);this.shipping.updateEstimate(this.items);}}
After the refactoring
// Cart publishes events; listeners subscribe at the composition root.class Cart {constructor() {this.items = [];this.listeners = [];}subscribe(listener) {this.listeners.push(listener);}addItem(item, quantity) {this.items.push({ item, quantity });this.notify({ type: 'item-added', item, quantity, cart: this });}notify(event) {this.listeners.forEach((listener) => listener.handle(event));}}class InventoryListener {constructor(inventory) { this.inventory = inventory; }handle(event) {if (event.type === 'item-added') this.inventory.reserve(event.item, event.quantity);}}class AnalyticsListener {constructor(analytics) { this.analytics = analytics; }handle(event) {if (event.type === 'item-added') this.analytics.recordAdd(event.item, event.quantity);}}class ShippingListener {constructor(shipping) { this.shipping = shipping; }handle(event) {if (event.type === 'item-added') this.shipping.updateEstimate(event.cart.items);}}const cart = new Cart();cart.subscribe(new InventoryListener(inventory));cart.subscribe(new AnalyticsListener(analytics));cart.subscribe(new ShippingListener(shipping));
Hard-coded notifications couple the publisher to the full consumer surface. The agent's per-publisher-edit verification cost scales with consumer count; static analysis cannot enforce that the publisher's events fully describe its state changes.
Observer's dynamic dispatch defeats static call-graph analysis at the event boundary. The agent cannot statically determine which listeners fire on which events without reading the subscription wiring; ordering assumptions are invisible in the code.
Adding a consumer is one new observer class that subscribes to the publisher's protocol; publisher tests do not load consumer mocks, and the publisher's signature stays fixed across additions.
Subscription wiring scattered across the composition root requires the agent to grep for `subscribe(` calls to enumerate the consumer set. Stale subscriptions (uncleaned references) cause hard-to-debug memory and behaviour leaks the agent cannot detect statically.