Refactoring Guru | Observer

Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

Observer is used everywhere, especially in reactive environment like web UI.

Examples

Vue

Let’s use Vue’s reactivity design as an example. Read the docs for ref() and reactive() reactive() returns a reactive proxy of the object.

So Proxy pattern can be used to implement observer-design-pattern.

Vue’s reactivity system, including ref(), is based on the Observer pattern combined with Proxies and dependency tracking.

How Vue Uses the Observer Pattern:

  1. Reactive State (ref, reactive):
    • When a reactive value is accessed (ref.value or reactiveObject.prop), Vue tracks which components or effects depend on it.
  2. Effect Tracking (Dependency Collection):
    • When a reactive value changes, Vue notifies all dependent components/effects to re-run.
  3. Reactivity System:
    • Vue maintains a global effect stack, so when a reactive value is read during execution, Vue knows which effect depends on it.

Sample Code

class Dep {
  constructor() {
    this.subscribers = new Set();
  }
 
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
 
  notify() {
    this.subscribers.forEach((sub) => sub());
  }
}
 
let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}
 
function reactive(obj) {
  const depMap = new Map(); // Track dependencies for each property
 
  return new Proxy(obj, {
    get(target, key) {
      if (!depMap.has(key)) {
        depMap.set(key, new Dep());
      }
      depMap.get(key).depend();
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      if (depMap.has(key)) {
        depMap.get(key).notify();
      }
      return true;
    }
  });
}
 
// Usage Example
const state = reactive({ count: 0 });
 
watchEffect(() => {
  console.log(`Count changed: ${state.count}`);
});
 
state.count++; // Triggers reactivity