Labs ICT
โญ Pro Login

Behavioral Patterns

Observer, Strategy, Command, and Iterator patterns for object communication.

Behavioral Patterns Overview

Behavioral patterns deal with how objects communicate and distribute responsibility. They focus on the interaction between objects and how the flow of control is managed.

Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Also known as Publish-Subscribe.

// Subject (Observable)
class EventEmitter {
  #listeners = {};

  on(event, callback) {
    if (!this.#listeners[event]) {
      this.#listeners[event] = [];
    }
    this.#listeners[event].push(callback);
    return () => this.off(event, callback); // unsubscribe function
  }

  off(event, callback) {
    if (!this.#listeners[event]) return;
    this.#listeners[event] = this.#listeners[event].filter(cb => cb !== callback);
  }

  emit(event, data) {
    if (!this.#listeners[event]) return;
    this.#listeners[event].forEach(callback => callback(data));
  }
}

// Concrete Subject
class ShoppingCart extends EventEmitter {
  #items = [];

  addItem(item) {
    this.#items.push(item);
    this.emit("itemAdded", { item, total: this.#items.length });
  }

  removeItem(itemId) {
    this.#items = this.#items.filter(i => i.id !== itemId);
    this.emit("itemRemoved", { itemId, total: this.#items.length });
  }

  checkout() {
    this.emit("checkout", { items: this.#items });
    this.#items = [];
  }
}

// Observers
class CartLogger {
  log(data) {
    console.log(`[LOG] Cart updated: ${data.total} items`);
  }
}

class CartUI {
  update(data) {
    console.log(`[UI] Cart badge: ${data.total} items`);
  }
}

class CartAnalytics {
  track(data) {
    console.log(`[ANALYTICS] Item action: ${data.total} items in cart`);
  }
}

// Subscribe
const cart = new ShoppingCart();
const logger = new CartLogger();
const ui = new CartUI();
const analytics = new CartAnalytics();

cart.on("itemAdded", (data) => logger.log(data));
cart.on("itemAdded", (data) => ui.update(data));
cart.on("itemAdded", (data) => analytics.track(data));

// All three observers notified automatically
cart.addItem({ id: 1, name: "Laptop" });
// [LOG] Cart updated: 1 items
// [UI] Cart badge: 1 items
// [ANALYTICS] Item action: 1 items in cart

Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

// Strategy interface
class SortStrategy {
  sort(data) { throw new Error("Implement sort()"); }
}

// Concrete strategies
class BubbleSort extends SortStrategy {
  sort(data) {
    console.log("Sorting with Bubble Sort (O(nยฒ))");
    return [...data].sort((a, b) => a - b);
  }
}

class QuickSort extends SortStrategy {
  sort(data) {
    console.log("Sorting with Quick Sort (O(n log n))");
    return [...data].sort((a, b) => a - b);
  }
}

class InsertionSort extends SortStrategy {
  sort(data) {
    console.log("Sorting with Insertion Sort");
    return [...data].sort((a, b) => a - b);
  }
}

// Context โ€” uses a strategy
class DataProcessor {
  #strategy;

  constructor(strategy = new BubbleSort()) {
    this.#strategy = strategy;
  }

  setStrategy(strategy) {
    this.#strategy = strategy;
  }

  processData(data) {
    console.log(`Processing ${data.length} elements...`);
    return this.#strategy.sort(data);
  }
}

// Switch strategies at runtime
const processor = new DataProcessor();
const data = [64, 34, 25, 12, 22, 11, 90];

processor.setStrategy(new BubbleSort());
processor.processData(data);  // Uses Bubble Sort

processor.setStrategy(new QuickSort());
processor.processData(data);  // Uses Quick Sort

Command Pattern

Encapsulates a request as an object, allowing you to parameterize clients with different requests, queue requests, and support undoable operations.

// Command interface
class Command {
  execute() { throw new Error("Implement me"); }
  undo() { throw new Error("Implement me"); }
}

// Receiver
class TextEditor {
  #content = "";

  type(text) { this.#content += text; }
  delete(n) { this.#content = this.#content.slice(0, -n); }
  getContent() { return this.#content; }
}

// Concrete commands
class TypeCommand extends Command {
  #editor;
  #text;

  constructor(editor, text) {
    super();
    this.#editor = editor;
    this.#text = text;
  }

  execute() { this.#editor.type(this.#text); }
  undo() { this.#editor.delete(this.#text.length); }
}

class DeleteCommand extends Command {
  #editor;
  #count;
  #deletedText = "";

  constructor(editor, count) {
    super();
    this.#editor = editor;
    this.#count = count;
  }

  execute() {
    this.#deletedText = this.#editor.getContent().slice(-this.#count);
    this.#editor.delete(this.#count);
  }

  undo() { this.#editor.type(this.#deletedText); }
}

// Invoker
class CommandHistory {
  #history = [];
  #undone = [];

  execute(command) {
    command.execute();
    this.#history.push(command);
    this.#undone = [];
  }

  undo() {
    if (this.#history.length === 0) return;
    const command = this.#history.pop();
    command.undo();
    this.#undone.push(command);
  }

  redo() {
    if (this.#undone.length === 0) return;
    const command = this.#undone.pop();
    command.execute();
    this.#history.push(command);
  }
}

// Usage
const editor = new TextEditor();
const history = new CommandHistory();

history.execute(new TypeCommand(editor, "Hello"));
history.execute(new TypeCommand(editor, " World"));
console.log(editor.getContent()); // "Hello World"

history.undo();
console.log(editor.getContent()); // "Hello"

history.redo();
console.log(editor.getContent()); // "Hello World"

Other Key Behavioral Patterns

State Pattern:
  Allows an object to change its behavior when its internal state changes.
  Similar to Strategy, but the state itself triggers behavior changes.

  Context โ†’ State A โ†’ State B โ†’ State C
  Each state defines its own behavior.

Template Method:
  Defines the skeleton of an algorithm in a method, deferring some steps
  to subclasses. Subclasses override specific steps without changing
  the algorithm's structure.

  abstract class DataMiner {
    mine() {            // template method
      openFile();
      extractData();
      parseData();
      analyzeData();
      closeFile();
    }
    abstract extractData();
    abstract parseData();
  }

Mediator:
  Defines an object that encapsulates how a set of objects interact.
  Promotes loose coupling by preventing objects from referring to each
  other explicitly.

  ChatRoom (mediator) โ†โ†’ User1
                      โ†โ†’ User2
                      โ†โ†’ User3
  Users don't talk directly โ€” the ChatRoom coordinates.

Memento:
  Provides the ability to capture and externalize an object's internal
  state so it can be restored later, without violating encapsulation.

  Editor โ†’ save() โ†’ Memento { content, cursor }
  Editor โ† restore() โ† Memento

๐Ÿงช Quick Quiz

Which pattern allows objects to be notified of changes in another object without tight coupling?