Behavioral design patterns deal with how objects interact and communicate with each other. They help in managing the responsibilities and behaviors of objects in a system
Here are some examples of common behavioral design patterns:
1. Observer Pattern:
The Observer pattern defines a one-to-many relationship between objects. When one object changes state, all its dependents are notified and updated automatically.
Intent:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Example:
Event handling in web applications, where multiple subscribers (observers) listen to changes in an event source (subject).
For ex:
class Subject { constructor() { this.observers = []; } addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { const index = this.observers.indexOf(observer); if (index !== -1) { this.observers.splice(index, 1); } } notify(message) { this.observers.forEach(observer => observer.update(message)); } } class Observer { constructor(name) { this.name = name; } update(message) { console.log(`${this.name} received message: ${message}`); } } // Usage const subject = new Subject(); const observer1 = new Observer('Observer 1'); const observer2 = new Observer('Observer 2'); subject.addObserver(observer1); subject.addObserver(observer2); subject.notify('Hello, Observers');
2. Strategy Pattern:
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the client to choose the appropriate algorithm at runtime.
Intent:
Define a family of algorithms, encapsulate each one, and make them interchangeable. The strategy pattern allows the client to choose the appropriate algorithm at runtime.
Example:
Sorting algorithms in a sorting application, where you can dynamically switch between different sorting strategies.
For ex.
class PaymentStrategy { pay(amount) {} } class CreditCardPayment extends PaymentStrategy { pay(amount) { console.log(`Paid $${amount} via Credit Card`); } } class PayPalPayment extends PaymentStrategy { pay(amount) { console.log(`Paid $${amount} via PayPal`); } } class ShoppingCart { constructor(paymentStrategy) { this.paymentStrategy = paymentStrategy; } checkout(amount) { this.paymentStrategy.pay(amount); } } // Usage const cart1 = new ShoppingCart(new CreditCardPayment()); const cart2 = new ShoppingCart(new PayPalPayment()); cart1.checkout(100); // Output: Paid $100 via Credit Card cart2.checkout(50); // Output: Paid $50 via PayPal
3. Command Pattern:
The Command pattern encapsulates a request as an object, thereby allowing us to parameterize clients with queues, requests, and operations.
Intent:
Encapsulate a request as an object, thereby allowing us to parameterize clients with queues, requests, and operations. It also provides support for undoable operations.
Example:
A remote control for various electronic devices, where each button press corresponds to a command that can be executed and undone.
For ex:
class Light { turnOn() { console.log('Light is on'); } turnOff() { console.log('Light is off'); } } class Command { constructor(light) { this.light = light; } execute() {} } class TurnOnCommand extends Command { execute() { this.light.turnOn(); } } class TurnOffCommand extends Command { execute() { this.light.turnOff(); } } class RemoteControl { constructor() { this.commands = []; } addCommand(command) { this.commands.push(command); } pressButton() { this.commands.forEach(command => command.execute()); } } // Usage const light = new Light(); const turnOn = new TurnOnCommand(light); const turnOff = new TurnOffCommand(light); const remote = new RemoteControl(); remote.addCommand(turnOn); remote.addCommand(turnOff); remote.pressButton(); // Output: Light is on, Light is off
4. Chain of Responsibility Pattern:
The Chain of Responsibility Pattern is a design pattern that allows you to pass requests along a chain of handlers. Each handler decides whether to process the request or pass it to the next handler in the chain. Here's a JavaScript example of the Chain of Responsibility Pattern:
Intent:
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
Example:
Middleware in web frameworks, where each middleware component processes a part of an HTTP request.
// Define a base handler with a reference to the next handler in the chain class Handler { constructor() { this.nextHandler = null; } setNextHandler(handler) { this.nextHandler = handler; } handleRequest(request) { if (this.nextHandler) { this.nextHandler.handleRequest(request); } } } // Concrete handlers class ConcreteHandlerA extends Handler { handleRequest(request) { if (request === 'A') { console.log('Handler A processed the request'); } else { super.handleRequest(request); } } } class ConcreteHandlerB extends Handler { handleRequest(request) { if (request === 'B') { console.log('Handler B processed the request'); } else { super.handleRequest(request); } } } class ConcreteHandlerC extends Handler { handleRequest(request) { if (request === 'C') { console.log('Handler C processed the request'); } else { console.log('Request cannot be handled.'); } } } // Usage const handlerA = new ConcreteHandlerA(); const handlerB = new ConcreteHandlerB(); const handlerC = new ConcreteHandlerC(); handlerA.setNextHandler(handlerB); handlerB.setNextHandler(handlerC); handlerA.handleRequest('A'); // Output: Handler A processed the request handlerA.handleRequest('B'); // Output: Handler B processed the request handlerA.handleRequest('C'); // Output: Handler C processed the request handlerA.handleRequest('D'); // Output: Request cannot be handled.
In this example:
Handler
is the base handler class that defines the structure of the chain and a reference to the next handler.- Concrete handlers like
ConcreteHandlerA
,ConcreteHandlerB
, andConcreteHandlerC
inherit from the baseHandler
class and provide their own implementations of thehandleRequest
method. - The client sets up the chain of handlers by linking them using the
setNextHandler
method. - When a request is made, it is passed through the chain of handlers. If a handler can process the request, it does so; otherwise, it passes the request to the next handler in the chain.
The Chain of Responsibility Pattern helps decouple the sender and receiver of a request and allows multiple objects to handle the request without the sender knowing which object ultimately processes it.
5. State Pattern:
The State Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes.
Intent:
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Example:
A video player that transitions between different states like playing, pausing, or stopping.
Here's a JavaScript example of the State Pattern:
// Context class that maintains a reference to the current state class Context { constructor() { this.state = null; } setState(state) { this.state = state; } request() { if (this.state) { this.state.handle(); } } } // State interface with a handle method class State { handle() {} } // Concrete states class StateA extends State { handle() { console.log('State A is handling the request.'); } } class StateB extends State { handle() { console.log('State B is handling the request.'); } } class StateC extends State { handle() { console.log('State C is handling the request.'); } } // Usage const context = new Context(); const stateA = new StateA(); const stateB = new StateB(); const stateC = new StateC(); context.setState(stateA); context.request(); // Output: State A is handling the request. context.setState(stateB); context.request(); // Output: State B is handling the request. context.setState(stateC); context.request(); // Output: State C is handling the request.
In this example:
- The
Context
class maintains a reference to the current state and has arequest
method to trigger state-specific behavior. - The
State
interface defines ahandle
method that concrete states must implement. - Concrete states like
StateA
,StateB
, andStateC
implement thehandle
method with their specific behavior. - The client (usage) sets the initial state in the context and triggers the request, which delegates to the current state's
handle
method.
The State Pattern is useful when an object's behavior depends on its internal state, and it allows for easy addition of new states without modifying the context class. It promotes encapsulation and reduces conditionals in the code.
6. Visitor Pattern:
The Visitor Pattern is a behavioral design pattern that allows you to add new operations to objects without having to modify their class. It is particularly useful when working with complex object structures.
Intent:
Represent an operation to be performed on the elements of an object structure. Visitors allow you to add further operations without having to modify the elements themselves.
Example:
A document structure with different types of elements like paragraphs, images, and tables, where a visitor can be used to perform various operations like rendering or counting elements.
Here's a JavaScript example of the Visitor Pattern:
// Define the Visitor interface with visit methods for different elements class Visitor { visitElementA(element) {} visitElementB(element) {} } // Define concrete elements that accept visitors class ElementA { accept(visitor) { visitor.visitElementA(this); } operationA() { console.log('Operation A on Element A'); } } class ElementB { accept(visitor) { visitor.visitElementB(this); } operationB() { console.log('Operation B on Element B'); } } // Concrete visitor that defines the operations to be performed on elements class ConcreteVisitor extends Visitor { visitElementA(element) { element.operationA(); } visitElementB(element) { element.operationB(); } } // Usage const elementA = new ElementA(); const elementB = new ElementB(); const visitor = new ConcreteVisitor(); elementA.accept(visitor); // Output: Operation A on Element A elementB.accept(visitor); // Output: Operation B on Element B
In this example:
Visitor
is an interface that defines thevisitElementA
andvisitElementB
methods for different elements.ElementA
andElementB
are concrete elements that implement anaccept
method. Theaccept
method allows the elements to accept a visitor and delegate the operation to the visitor.ConcreteVisitor
is a concrete visitor that defines the operations to be performed on elements. It implements thevisitElementA
andvisitElementB
methods, which specify what to do when visiting each type of element.- In the usage part,
elementA
andelementB
accept thevisitor
, and the visitor performs operations on them without modifying the element classes.
The Visitor Pattern is helpful when you need to perform operations on complex object structures without altering their structure. It separates the algorithm from the elements, making it easier to add new operations.
7. Memento Pattern:
The Memento Pattern is a behavioral design pattern that allows an object's internal state to be captured and restored without exposing its details. It's useful for implementing features like undo/redo functionality.
Intent:
Capture and externalize an object's internal state so that the object can be restored to this state later.
Example:
Undo/redo functionality in a text editor, where you can save and restore the document's previous states.
Here's a JavaScript example of the Memento Pattern:
// Originator class (the object whose state needs to be saved) class Originator { constructor(state) { this.state = state; } // Create a memento with the current state createMemento() { return new Memento(this.state); } // Restore the state from a memento restoreFromMemento(memento) { this.state = memento.getState(); } // Getter and setter for the state getState() { return this.state; } setState(state) { this.state = state; } } // Memento class (stores the state of the Originator) class Memento { constructor(state) { this.state = state; } getState() { return this.state; } } // Caretaker class (responsible for keeping track of multiple mementos) class Caretaker { constructor() { this.mementos = []; } addMemento(memento) { this.mementos.push(memento); } getMemento(index) { return this.mementos[index]; } } // Usage const originator = new Originator('State 1'); const caretaker = new Caretaker(); caretaker.addMemento(originator.createMemento()); // Save the initial state originator.setState('State 2'); // Change the state caretaker.addMemento(originator.createMemento()); // Save the new state console.log('Current State:', originator.getState()); originator.restoreFromMemento(caretaker.getMemento(0)); // Restore the initial state console.log('Restored State:', originator.getState());
In this example:
- The
Originator
class is the object whose state needs to be saved. It can create mementos to capture its current state and restore its state from a memento. - The
Memento
class is used to store the state of theOriginator
. - The
Caretaker
class keeps track of multiple mementos. It can add mementos to its collection and retrieve them. - In the usage part, the initial state of the
Originator
is saved as a memento usingcreateMemento
. The state is then changed to a new value, and another memento is created and saved. Finally, the state is restored from the first memento usingrestoreFromMemento
.
The Memento Pattern is a powerful way to implement features like undo/redo or to save and restore an object's state without exposing its internals. It provides a way to decouple the originator from the details of the state storage.
8. Interpreter Pattern:
The Interpreter Pattern is a behavioral design pattern that is used to define a language's grammar and provides an interpreter to interpret and execute expressions in that language. Here's a simple
Intent:
Provide a way to evaluate language grammar or expressions. It defines grammar for a simple language and provides an interpreter for the language.
Example:
A regular expression parser, where it interprets and matches strings based on a regular expression pattern.
JavaScript example of the Interpreter Pattern for evaluating arithmetic expressions:
// Abstract Expression class class Expression { interpret(context) { // To be implemented by concrete expressions } } // Terminal Expression class NumberExpression extends Expression { constructor(value) { super(); this.value = value; } interpret(context) { return this.value; } } // Non-terminal Expression (Addition) class AdditionExpression extends Expression { constructor(left, right) { super(); this.left = left; this.right = right; } interpret(context) { return this.left.interpret(context) + this.right.interpret(context); } } // Non-terminal Expression (Subtraction) class SubtractionExpression extends Expression { constructor(left, right) { super(); this.left = left; this.right = right; } interpret(context) { return this.left.interpret(context) - this.right.interpret(context); } } // Context to hold variables and interpret expressions class Context { constructor() { this.variables = {}; } setVariable(name, value) { this.variables[name] = value; } getVariable(name) { return this.variables[name]; } } // Usage const context = new Context(); context.setVariable('x', 10); context.setVariable('y', 5); const expression = new AdditionExpression( new NumberExpression(context.getVariable('x')), new SubtractionExpression( new NumberExpression(context.getVariable('y')), new NumberExpression(2) ) ); const result = expression.interpret(context); console.log('Result:', result); // Output: Result: 13
In this example:
- The
Expression
class is the abstract expression that defines theinterpret
method. Subclasses implement this method for specific types of expressions. NumberExpression
is a terminal expression that interprets and returns a numeric value.AdditionExpression
andSubtractionExpression
are non-terminal expressions that represent addition and subtraction operations. They evaluate their left and right expressions.- The
Context
class holds variables and provides a way to set and get variable values. - In the usage part, a context is created and variables 'x' and 'y' are set. An arithmetic expression is constructed using terminal and non-terminal expressions. Finally, the expression is interpreted, and the result is computed.
The Interpreter Pattern can be used to build interpreters for various domain-specific languages or to define rules for parsing and interpreting expressions. It decouples the grammar and interpretation from the client code.
9. Template Method Pattern:
The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in the base class but lets subclasses override specific steps of the algorithm without changing its structure.
Intent:
Define the skeleton of an algorithm in the base class but let subclasses override specific steps of the algorithm without changing its structure.
Example:
In a game development framework, a base class for game objects defines the game loop, collision detection, and rendering, while concrete subclasses implement game-specific behavior.
Here's a JavaScript example of the Template Method Pattern:
// Abstract class defining the template method class AbstractClass { templateMethod() { this.step1(); this.step2(); this.step3(); } // Abstract methods to be implemented by subclasses step1() { throw new Error('Abstract method step1 must be implemented'); } step2() { throw new Error('Abstract method step2 must be implemented'); } step3() { throw new Error('Abstract method step3 must be implemented'); } } // Concrete subclass that implements the abstract methods class ConcreteClass extends AbstractClass { step1() { console.log('ConcreteClass: Step 1'); } step2() { console.log('ConcreteClass: Step 2'); } step3() { console.log('ConcreteClass: Step 3'); } } // Usage const concreteInstance = new ConcreteClass(); concreteInstance.templateMethod();
In this example:
AbstractClass
is an abstract class that defines the template methodtemplateMethod
. It also declares three abstract methodsstep1
,step2
, andstep3
that are meant to be overridden by concrete subclasses.ConcreteClass
is a concrete subclass that extendsAbstractClass
and implements the abstract methodsstep1
,step2
, andstep3
.- In the usage part, an instance of
ConcreteClass
is created and thetemplateMethod
is called, which executes the steps in a predefined order. The concrete subclass provides its own implementation for each step.
The Template Method Pattern is useful when you have an algorithm that follows a common structure but allows some variation in the specific steps. It promotes reusability of code and enforces the structure of the algorithm.