Behavioral patterns focus on communication between objects, defining how objects interact and distribute responsibility. They characterize complex control flow that’s difficult to follow at runtime.
Chain of Responsibility
Problem: Multiple objects may handle a request, but the handler isn’t known in advance.
When to use:
- More than one object may handle request, handler unknown a priori
- Want to issue request to one of several objects without specifying receiver
- Set of objects that can handle request should be specified dynamically
Structure:
- Handler defines interface for handling requests
- Concrete handlers handle requests or pass to successor
- Client initiates request
Example (TypeScript):
interface Handler {
setNext(handler: Handler): Handler;
handle(request: string): string | null;
}
abstract class AbstractHandler implements Handler {
private nextHandler?: Handler;
setNext(handler: Handler): Handler {
this.nextHandler = handler;
return handler;
}
handle(request: string): string | null {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class SupportLevel1 extends AbstractHandler {
handle(request: string): string | null {
if (request === 'password-reset') {
return 'L1: Password reset email sent';
}
return super.handle(request);
}
}
class SupportLevel2 extends AbstractHandler {
handle(request: string): string | null {
if (request === 'account-locked') {
return 'L2: Account unlocked';
}
return super.handle(request);
}
}
class SupportLevel3 extends AbstractHandler {
handle(request: string): string | null {
if (request === 'data-corruption') {
return 'L3: Data restored from backup';
}
return super.handle(request);
}
}
// Usage
const l1 = new SupportLevel1();
const l2 = new SupportLevel2();
const l3 = new SupportLevel3();
l1.setNext(l2).setNext(l3);
console.log(l1.handle('password-reset')); // L1 handles
console.log(l1.handle('account-locked')); // L2 handles
console.log(l1.handle('data-corruption')); // L3 handlesExample (Ruby):
class Handler
attr_writer :next_handler
def handle(request)
return @next_handler.handle(request) if @next_handler
nil
end
end
class SupportLevel1 < Handler
def handle(request)
return 'L1: Password reset email sent' if request == 'password-reset'
super
end
end
class SupportLevel2 < Handler
def handle(request)
return 'L2: Account unlocked' if request == 'account-locked'
super
end
end
# Usage
l1 = SupportLevel1.new
l2 = SupportLevel2.new
l1.next_handler = l2
puts l1.handle('password-reset')
puts l1.handle('account-locked')Command
Problem: Need to encapsulate requests as objects, allowing parameterization and queuing.
When to use:
- Parameterize objects with operations
- Specify, queue, and execute requests at different times
- Support undo/redo
- Support logging changes
- Structure system around high-level operations
Structure:
- Command declares interface for executing operations
- Concrete command implements execute by invoking operations on receiver
- Invoker asks command to carry out request
- Receiver knows how to perform operations
Example (TypeScript):
// Receiver
class Light {
on() { console.log('Light is ON'); }
off() { console.log('Light is OFF'); }
}
// Command interface
interface Command {
execute(): void;
undo(): void;
}
// Concrete commands
class LightOnCommand implements Command {
constructor(private light: Light) {}
execute() {
this.light.on();
}
undo() {
this.light.off();
}
}
class LightOffCommand implements Command {
constructor(private light: Light) {}
execute() {
this.light.off();
}
undo() {
this.light.on();
}
}
// Invoker
class RemoteControl {
private history: Command[] = [];
submit(command: 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.submit(new LightOnCommand(light));
remote.submit(new LightOffCommand(light));
remote.undo(); // Turns light back onExample (Ruby):
class Light
def on
puts 'Light is ON'
end
def off
puts 'Light is OFF'
end
end
class Command
def execute
raise NotImplementedError
end
def undo
raise NotImplementedError
end
end
class LightOnCommand < Command
def initialize(light)
@light = light
end
def execute
@light.on
end
def undo
@light.off
end
end
class RemoteControl
def initialize
@history = []
end
def submit(command)
command.execute
@history << command
end
def undo
command = @history.pop
command&.undo
end
endIterator
Problem: Need to access elements of a collection sequentially without exposing representation.
When to use:
- Access aggregate object’s contents without exposing representation
- Support multiple traversals of aggregate objects
- Provide uniform interface for traversing different structures
Note: Most modern languages have built-in iterators. This pattern is more relevant when creating custom collection types.
Example (TypeScript):
interface Iterator<T> {
current(): T;
next(): T;
hasNext(): boolean;
}
interface Aggregator<T> {
createIterator(): Iterator<T>;
}
class BookIterator implements Iterator<string> {
private position: number = 0;
constructor(private books: string[]) {}
current(): string {
return this.books[this.position];
}
next(): string {
return this.books[++this.position];
}
hasNext(): boolean {
return this.position < this.books.length - 1;
}
}
class BookCollection implements Aggregator<string> {
private books: string[] = [];
addBook(book: string) {
this.books.push(book);
}
createIterator(): Iterator<string> {
return new BookIterator(this.books);
}
}
// Usage
const collection = new BookCollection();
collection.addBook('Design Patterns');
collection.addBook('Clean Code');
collection.addBook('Refactoring');
const iterator = collection.createIterator();
while (iterator.hasNext()) {
console.log(iterator.current());
iterator.next();
}
console.log(iterator.current()); // Last bookMediator
Problem: Objects communicate in complex ways; need to decouple their interactions.
When to use:
- Set of objects communicate in well-defined but complex ways
- Reusing object is difficult because it refers to many others
- Behavior distributed between several classes should be customizable
Structure:
- Mediator defines interface for communicating with colleagues
- Concrete mediator coordinates colleagues
- Colleagues communicate through mediator
Example (TypeScript):
interface Mediator {
notify(sender: Component, event: string): void;
}
class Component {
constructor(protected mediator: Mediator) {}
}
class Button extends Component {
click() {
console.log('Button clicked');
this.mediator.notify(this, 'click');
}
}
class TextBox extends Component {
setText(text: string) {
console.log(`TextBox: ${text}`);
}
}
class Checkbox extends Component {
check() {
console.log('Checkbox checked');
this.mediator.notify(this, 'check');
}
}
class Dialog implements Mediator {
constructor(
private button: Button,
private textBox: TextBox,
private checkbox: Checkbox
) {}
notify(sender: Component, event: string) {
if (sender === this.button && event === 'click') {
this.textBox.setText('Button was clicked');
}
if (sender === this.checkbox && event === 'check') {
this.textBox.setText('Checkbox was checked');
}
}
}
// Usage
const button = new Button(null as any);
const textBox = new TextBox(null as any);
const checkbox = new Checkbox(null as any);
const dialog = new Dialog(button, textBox, checkbox);
(button as any).mediator = dialog;
(textBox as any).mediator = dialog;
(checkbox as any).mediator = dialog;
button.click();
checkbox.check();Memento
Problem: Need to capture and restore an object’s internal state without violating encapsulation.
When to use:
- Snapshot of object’s state must be saved for later restoration
- Direct interface to obtain state would expose implementation
Structure:
- Memento stores internal state of originator
- Originator creates memento and uses it to restore state
- Caretaker keeps mementos but never operates on them
Example (TypeScript):
// Memento
class EditorMemento {
constructor(private readonly content: string) {}
getContent(): string {
return this.content;
}
}
// Originator
class Editor {
private content: string = '';
type(words: string) {
this.content += words;
}
getContent(): string {
return this.content;
}
save(): EditorMemento {
return new EditorMemento(this.content);
}
restore(memento: EditorMemento) {
this.content = memento.getContent();
}
}
// Caretaker
class History {
private mementos: EditorMemento[] = [];
push(memento: EditorMemento) {
this.mementos.push(memento);
}
pop(): EditorMemento | undefined {
return this.mementos.pop();
}
}
// Usage
const editor = new Editor();
const history = new History();
editor.type('Hello ');
history.push(editor.save());
editor.type('World!');
console.log(editor.getContent()); // "Hello World!"
editor.restore(history.pop()!);
console.log(editor.getContent()); // "Hello "Observer
Problem: When one object changes state, all dependents need to be notified automatically.
When to use:
- Abstraction has two aspects, one dependent on the other
- Change to one object requires changing others
- Object should notify other objects without knowing who they are
Structure:
- Subject maintains list of observers and notifies them
- Observer defines updating interface
- Concrete observers implement update to sync with subject
Example (TypeScript):
interface Observer {
update(subject: Subject): void;
}
class Subject {
private observers: Observer[] = [];
private state: number = 0;
attach(observer: Observer) {
this.observers.push(observer);
}
detach(observer: Observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify() {
this.observers.forEach(observer => observer.update(this));
}
getState(): number {
return this.state;
}
setState(state: number) {
this.state = state;
this.notify();
}
}
class ConcreteObserver implements Observer {
constructor(private name: string) {}
update(subject: Subject) {
console.log(`${this.name} notified. New state: ${subject.getState()}`);
}
}
// Usage
const subject = new Subject();
const observer1 = new ConcreteObserver('Observer 1');
const observer2 = new ConcreteObserver('Observer 2');
subject.attach(observer1);
subject.attach(observer2);
subject.setState(10); // Both observers notified
subject.setState(20); // Both observers notifiedResources:
State
Problem: Object behavior changes based on its internal state.
When to use:
- Object behavior depends on state and must change at runtime
- Operations have large conditional statements that depend on state
Structure:
- Context maintains instance of concrete state subclass
- State defines interface for encapsulating behavior
- Concrete states implement behavior for particular state
Example (TypeScript):
interface State {
handle(context: Context): void;
}
class Context {
private state: State;
constructor(initialState: State) {
this.state = initialState;
}
setState(state: State) {
this.state = state;
}
request() {
this.state.handle(this);
}
}
class ConcreteStateA implements State {
handle(context: Context) {
console.log('State A handling request');
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
handle(context: Context) {
console.log('State B handling request');
context.setState(new ConcreteStateA());
}
}
// Usage
const context = new Context(new ConcreteStateA());
context.request(); // State A
context.request(); // State B
context.request(); // State AReal-world example (Document states):
interface DocumentState {
publish(doc: Document): void;
render(): string;
}
class Document {
private state: DocumentState;
constructor() {
this.state = new DraftState();
}
setState(state: DocumentState) {
this.state = state;
}
publish() {
this.state.publish(this);
}
render(): string {
return this.state.render();
}
}
class DraftState implements DocumentState {
publish(doc: Document) {
console.log('Publishing draft...');
doc.setState(new ModerationState());
}
render(): string {
return 'Draft (not visible to public)';
}
}
class ModerationState implements DocumentState {
publish(doc: Document) {
console.log('Approving document...');
doc.setState(new PublishedState());
}
render(): string {
return 'In moderation (visible to moderators)';
}
}
class PublishedState implements DocumentState {
publish(doc: Document) {
console.log('Document already published');
}
render(): string {
return 'Published (visible to everyone)';
}
}
// Usage
const doc = new Document();
console.log(doc.render()); // Draft
doc.publish();
console.log(doc.render()); // In moderation
doc.publish();
console.log(doc.render()); // PublishedStrategy
Problem: Need to encapsulate interchangeable algorithms and select at runtime.
When to use:
- Many related classes differ only in behavior
- Need different variants of algorithm
- Algorithm uses data clients shouldn’t know about
- Class defines many behaviors appearing as conditionals
Structure:
- Strategy defines common interface for algorithms
- Concrete strategies implement different algorithms
- Context uses strategy interface
Example (TypeScript):
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardStrategy implements PaymentStrategy {
constructor(
private cardNumber: string,
private cvv: string
) {}
pay(amount: number) {
console.log(`Paid $${amount} with credit card ${this.cardNumber}`);
}
}
class PayPalStrategy implements PaymentStrategy {
constructor(private email: string) {}
pay(amount: number) {
console.log(`Paid $${amount} via PayPal to ${this.email}`);
}
}
class CryptoStrategy implements PaymentStrategy {
constructor(private walletAddress: string) {}
pay(amount: number) {
console.log(`Paid $${amount} in crypto to ${this.walletAddress}`);
}
}
class ShoppingCart {
private items: number[] = [];
constructor(private paymentStrategy: PaymentStrategy) {}
addItem(price: number) {
this.items.push(price);
}
checkout() {
const total = this.items.reduce((sum, price) => sum + price, 0);
this.paymentStrategy.pay(total);
}
setPaymentStrategy(strategy: PaymentStrategy) {
this.paymentStrategy = strategy;
}
}
// Usage
const cart = new ShoppingCart(new CreditCardStrategy('1234-5678', '123'));
cart.addItem(100);
cart.addItem(50);
cart.checkout();
cart.setPaymentStrategy(new PayPalStrategy('user@example.com'));
cart.checkout();Resources:
Related: Composition over Inheritance
Template Method
Problem: Define algorithm skeleton, letting subclasses override specific steps.
When to use:
- Implement invariant parts of algorithm once, let subclasses vary behavior
- Control subclass extensions (hooks)
- Factor common behavior among subclasses
Structure:
- Abstract class defines template method and primitive operations
- Concrete classes implement primitive operations
Example (TypeScript):
abstract class DataMiner {
// Template method
mine(path: string) {
const file = this.openFile(path);
const data = this.extractData(file);
const parsed = this.parseData(data);
const analyzed = this.analyzeData(parsed);
this.sendReport(analyzed);
this.closeFile(file);
}
// Concrete operations (same for all subclasses)
openFile(path: string): File {
console.log(`Opening file: ${path}`);
return { path } as File;
}
closeFile(file: File) {
console.log(`Closing file: ${file.path}`);
}
// Abstract operations (must be implemented by subclasses)
abstract extractData(file: File): string;
abstract parseData(data: string): any;
// Hook (can be overridden)
analyzeData(data: any): any {
console.log('Default analysis');
return data;
}
sendReport(data: any) {
console.log('Sending report...');
}
}
class PDFDataMiner extends DataMiner {
extractData(file: File): string {
console.log('Extracting data from PDF');
return 'pdf data';
}
parseData(data: string): any {
console.log('Parsing PDF data');
return { type: 'pdf', content: data };
}
}
class CSVDataMiner extends DataMiner {
extractData(file: File): string {
console.log('Extracting data from CSV');
return 'csv data';
}
parseData(data: string): any {
console.log('Parsing CSV data');
return { type: 'csv', content: data };
}
analyzeData(data: any): any {
console.log('Custom CSV analysis');
return data;
}
}
// Usage
const pdfMiner = new PDFDataMiner();
pdfMiner.mine('data.pdf');
const csvMiner = new CSVDataMiner();
csvMiner.mine('data.csv');
interface File { path: string; }Visitor
Problem: Add new operations to object structure without changing classes.
When to use:
- Object structure contains many classes with differing interfaces
- Many distinct and unrelated operations need to be performed
- Classes defining object structure rarely change, but operations change often
Structure:
- Visitor declares visit operation for each concrete element
- Concrete visitor implements operations
- Element defines accept method taking visitor
- Object structure can enumerate elements
Example (TypeScript):
// Visitor interface
interface ShapeVisitor {
visitCircle(circle: Circle): void;
visitSquare(square: Square): void;
}
// Element interface
interface Shape {
accept(visitor: ShapeVisitor): void;
}
// Concrete elements
class Circle implements Shape {
constructor(public radius: number) {}
accept(visitor: ShapeVisitor) {
visitor.visitCircle(this);
}
}
class Square implements Shape {
constructor(public side: number) {}
accept(visitor: ShapeVisitor) {
visitor.visitSquare(this);
}
}
// Concrete visitors
class AreaCalculator implements ShapeVisitor {
private totalArea = 0;
visitCircle(circle: Circle) {
const area = Math.PI * circle.radius * circle.radius;
console.log(`Circle area: ${area}`);
this.totalArea += area;
}
visitSquare(square: Square) {
const area = square.side * square.side;
console.log(`Square area: ${area}`);
this.totalArea += area;
}
getTotalArea(): number {
return this.totalArea;
}
}
class PerimeterCalculator implements ShapeVisitor {
visitCircle(circle: Circle) {
const perimeter = 2 * Math.PI * circle.radius;
console.log(`Circle perimeter: ${perimeter}`);
}
visitSquare(square: Square) {
const perimeter = 4 * square.side;
console.log(`Square perimeter: ${perimeter}`);
}
}
// Usage
const shapes: Shape[] = [
new Circle(5),
new Square(4),
new Circle(3)
];
const areaCalc = new AreaCalculator();
shapes.forEach(shape => shape.accept(areaCalc));
console.log(`Total area: ${areaCalc.getTotalArea()}`);
const perimCalc = new PerimeterCalculator();
shapes.forEach(shape => shape.accept(perimCalc));Related Patterns
- Creational Patterns - Create objects used in behavioral patterns
- Structural Patterns - Compose objects that collaborate
- Design Patterns Overview