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