Behavioral patterns focus on object collaboration and responsibility distribution. This article covers Observer, Strategy, Command, State, Template Method, Iterator, and Chain of Responsibility patterns.
Introduction
Behavioral patterns provide:
- Object communication
- Responsibility distribution
- Flexible behavior
- Event handling
- Algorithm encapsulation
Understanding these patterns helps you:
- Manage object interactions
- Implement flexible algorithms
- Handle events effectively
- Distribute responsibilities
- Improve code organization
Observer Pattern
Basic Observer
// ✅ Good: Observer pattern
class Subject {
constructor() {
this.observers = [];
}
attach(observer) {
this.observers.push(observer);
}
detach(observer) {
this.observers = this.observers.filter(o => o !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
throw new Error('Must implement update');
}
}
class ConcreteObserver extends Observer {
constructor(name) {
super();
this.name = name;
}
update(data) {
console.log(`${this.name} received: ${data}`);
}
}
// Usage
const subject = new Subject();
const observer1 = new ConcreteObserver('Observer 1');
const observer2 = new ConcreteObserver('Observer 2');
subject.attach(observer1);
subject.attach(observer2);
subject.notify('Hello!');
// Observer 1 received: Hello!
// Observer 2 received: Hello!
Event Emitter
// ✅ Good: Event emitter pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
off(event, listener) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(l => l !== listener);
}
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(listener => listener(data));
}
}
once(event, listener) {
const onceWrapper = (data) => {
listener(data);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
}
// Usage
const emitter = new EventEmitter();
emitter.on('user:login', (user) => {
console.log(`${user} logged in`);
});
emitter.on('user:login', (user) => {
console.log(`Welcome ${user}!`);
});
emitter.emit('user:login', 'John');
// John logged in
// Welcome John!
Strategy Pattern
Basic Strategy
// ✅ Good: Strategy pattern
class PaymentStrategy {
pay(amount) {
throw new Error('Must implement pay');
}
}
class CreditCardPayment extends PaymentStrategy {
pay(amount) {
console.log(`Paying $${amount} with credit card`);
return true;
}
}
class PayPalPayment extends PaymentStrategy {
pay(amount) {
console.log(`Paying $${amount} with PayPal`);
return true;
}
}
class BitcoinPayment extends PaymentStrategy {
pay(amount) {
console.log(`Paying ${amount} BTC with Bitcoin`);
return true;
}
}
class ShoppingCart {
constructor(paymentStrategy) {
this.paymentStrategy = paymentStrategy;
this.total = 0;
}
addItem(price) {
this.total += price;
}
checkout() {
return this.paymentStrategy.pay(this.total);
}
}
// Usage
const cart = new ShoppingCart(new CreditCardPayment());
cart.addItem(50);
cart.addItem(30);
cart.checkout(); // Paying $80 with credit card
const cart2 = new ShoppingCart(new PayPalPayment());
cart2.addItem(100);
cart2.checkout(); // Paying $100 with PayPal
Sorting Strategy
// ✅ Good: Sorting strategy
class Sorter {
constructor(strategy) {
this.strategy = strategy;
}
sort(array) {
return this.strategy.sort(array);
}
}
class BubbleSort {
sort(array) {
console.log('Sorting with bubble sort');
return [...array].sort((a, b) => a - b);
}
}
class QuickSort {
sort(array) {
console.log('Sorting with quick sort');
return [...array].sort((a, b) => a - b);
}
}
// Usage
const data = [5, 2, 8, 1, 9];
const sorter1 = new Sorter(new BubbleSort());
console.log(sorter1.sort(data)); // [1, 2, 5, 8, 9]
const sorter2 = new Sorter(new QuickSort());
console.log(sorter2.sort(data)); // [1, 2, 5, 8, 9]
Command Pattern
// ✅ Good: Command pattern
class Command {
execute() {
throw new Error('Must implement execute');
}
undo() {
throw new Error('Must implement undo');
}
}
class Light {
constructor() {
this.isOn = false;
}
turnOn() {
this.isOn = true;
console.log('Light is on');
}
turnOff() {
this.isOn = false;
console.log('Light is off');
}
}
class TurnOnCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOn();
}
undo() {
this.light.turnOff();
}
}
class TurnOffCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOff();
}
undo() {
this.light.turnOn();
}
}
class RemoteControl {
constructor() {
this.commands = [];
this.history = [];
}
execute(command) {
command.execute();
this.history.push(command);
}
undo() {
const command = this.history.pop();
if (command) {
command.undo();
}
}
}
// Usage
const light = new Light();
const remote = new RemoteControl();
remote.execute(new TurnOnCommand(light)); // Light is on
remote.execute(new TurnOffCommand(light)); // Light is off
remote.undo(); // Light is on
remote.undo(); // Light is off
State Pattern
// ✅ Good: State pattern
class State {
handle(context) {
throw new Error('Must implement handle');
}
}
class SolidState extends State {
handle(context) {
console.log('Melting solid to liquid');
context.setState(new LiquidState());
}
}
class LiquidState extends State {
handle(context) {
console.log('Evaporating liquid to gas');
context.setState(new GasState());
}
}
class GasState extends State {
handle(context) {
console.log('Condensing gas to liquid');
context.setState(new LiquidState());
}
}
class Matter {
constructor() {
this.state = new SolidState();
}
setState(state) {
this.state = state;
}
changeState() {
this.state.handle(this);
}
}
// Usage
const matter = new Matter();
matter.changeState(); // Melting solid to liquid
matter.changeState(); // Evaporating liquid to gas
matter.changeState(); // Condensing gas to liquid
Template Method Pattern
// ✅ Good: Template method pattern
class DataProcessor {
process(data) {
const validated = this.validate(data);
const transformed = this.transform(validated);
const result = this.save(transformed);
return result;
}
validate(data) {
throw new Error('Must implement validate');
}
transform(data) {
throw new Error('Must implement transform');
}
save(data) {
throw new Error('Must implement save');
}
}
class CSVProcessor extends DataProcessor {
validate(data) {
console.log('Validating CSV data');
return data;
}
transform(data) {
console.log('Transforming CSV to objects');
return data.split('\n').map(line => line.split(','));
}
save(data) {
console.log('Saving to database');
return data;
}
}
class JSONProcessor extends DataProcessor {
validate(data) {
console.log('Validating JSON data');
return JSON.parse(data);
}
transform(data) {
console.log('Transforming JSON');
return data;
}
save(data) {
console.log('Saving to database');
return data;
}
}
// Usage
const csvProcessor = new CSVProcessor();
csvProcessor.process('name,age\nJohn,30');
const jsonProcessor = new JSONProcessor();
jsonProcessor.process('{"name":"John","age":30}');
Iterator Pattern
// ✅ Good: Iterator pattern
class Iterator {
hasNext() {
throw new Error('Must implement hasNext');
}
next() {
throw new Error('Must implement next');
}
}
class ArrayIterator extends Iterator {
constructor(array) {
super();
this.array = array;
this.index = 0;
}
hasNext() {
return this.index < this.array.length;
}
next() {
return this.array[this.index++];
}
}
class Collection {
constructor(items) {
this.items = items;
}
createIterator() {
return new ArrayIterator(this.items);
}
}
// Usage
const collection = new Collection([1, 2, 3, 4, 5]);
const iterator = collection.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
// 1, 2, 3, 4, 5
Chain of Responsibility Pattern
// ✅ Good: Chain of responsibility
class Handler {
constructor(successor = null) {
this.successor = successor;
}
handle(request) {
throw new Error('Must implement handle');
}
}
class AuthHandler extends Handler {
handle(request) {
if (!request.user) {
console.log('Auth failed');
return false;
}
console.log('Auth passed');
return this.successor ? this.successor.handle(request) : true;
}
}
class ValidationHandler extends Handler {
handle(request) {
if (!request.data) {
console.log('Validation failed');
return false;
}
console.log('Validation passed');
return this.successor ? this.successor.handle(request) : true;
}
}
class LoggingHandler extends Handler {
handle(request) {
console.log('Logging request');
return this.successor ? this.successor.handle(request) : true;
}
}
// Usage
const chain = new AuthHandler(
new ValidationHandler(
new LoggingHandler()
)
);
const request = { user: 'John', data: { name: 'John' } };
chain.handle(request);
// Auth passed
// Validation passed
// Logging request
Practical Examples
Form Validation
// ✅ Good: Form validation with chain of responsibility
class ValidationRule {
constructor(successor = null) {
this.successor = successor;
}
validate(data) {
throw new Error('Must implement validate');
}
}
class RequiredRule extends ValidationRule {
validate(data) {
if (!data.value) {
return { valid: false, error: 'Field is required' };
}
return this.successor ? this.successor.validate(data) : { valid: true };
}
}
class EmailRule extends ValidationRule {
validate(data) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.value)) {
return { valid: false, error: 'Invalid email format' };
}
return this.successor ? this.successor.validate(data) : { valid: true };
}
}
class LengthRule extends ValidationRule {
constructor(minLength, successor = null) {
super(successor);
this.minLength = minLength;
}
validate(data) {
if (data.value.length < this.minLength) {
return { valid: false, error: `Minimum length is ${this.minLength}` };
}
return this.successor ? this.successor.validate(data) : { valid: true };
}
}
// Usage
const emailValidator = new RequiredRule(
new EmailRule(
new LengthRule(5)
)
);
console.log(emailValidator.validate({ value: '' }));
// { valid: false, error: 'Field is required' }
console.log(emailValidator.validate({ value: 'invalid' }));
// { valid: false, error: 'Invalid email format' }
console.log(emailValidator.validate({ value: '[email protected]' }));
// { valid: true }
Notification System
// ✅ Good: Notification system with observer
class NotificationCenter {
constructor() {
this.subscribers = {};
}
subscribe(event, callback) {
if (!this.subscribers[event]) {
this.subscribers[event] = [];
}
this.subscribers[event].push(callback);
}
unsubscribe(event, callback) {
if (this.subscribers[event]) {
this.subscribers[event] = this.subscribers[event].filter(
cb => cb !== callback
);
}
}
publish(event, data) {
if (this.subscribers[event]) {
this.subscribers[event].forEach(callback => callback(data));
}
}
}
// Usage
const notificationCenter = new NotificationCenter();
notificationCenter.subscribe('user:login', (user) => {
console.log(`${user} logged in`);
});
notificationCenter.subscribe('user:login', (user) => {
console.log(`Send welcome email to ${user}`);
});
notificationCenter.publish('user:login', 'John');
// John logged in
// Send welcome email to John
Best Practices
- Use Observer for event handling:
// ✅ Good emitter.on('event', callback); // ❌ Bad if (condition) callback(); ```javascript - Use Strategy for algorithm selection:
// ✅ Good const processor = new Processor(strategy); // ❌ Bad if (type === 'a') { } else if (type === 'b') { } ```javascript - Use State for state management:
// ✅ Good context.setState(newState); // ❌ Bad if (state === 'a') { } else if (state === 'b') { } ```javascript
Common Mistakes
- Overusing Observer:
// ❌ Bad - too many observers emitter.on('event', cb1); emitter.on('event', cb2); emitter.on('event', cb3); // ✅ Good - group related observers emitter.on('event', aggregatedCallback); ```javascript - Command without undo:
// ❌ Bad class Command { execute() { } } // ✅ Good class Command { execute() { } undo() { } }
Summary
Behavioral patterns manage object interactions. Key takeaways:
- Observer: Event handling
- Strategy: Algorithm selection
- Command: Action encapsulation
- State: State management
- Template Method: Algorithm structure
- Iterator: Sequential access
- Chain of Responsibility: Request handling
- Improves flexibility
- Reduces coupling
- Enhances maintainability
Related Resources
- Behavioral Patterns - Refactoring Guru
- Observer Pattern - MDN
- Strategy Pattern - Wikipedia
- State Pattern - Wikipedia
- Design Patterns in JavaScript
Next Steps
- Learn about Dependency Injection
- Explore Architectural Patterns
- Study SOLID Principles
- Practice behavioral patterns
- Build event-driven systems
Comments