JavaScript Course

Advanced JavaScript Design Patterns

Creational Patterns

Creational patterns are design patterns that deal with object creation. They provide a way to create objects in a manner that is flexible and reusable, and that promotes code reuse.

There are many different creational patterns, each with its own specific purpose. Some of the most common creational patterns include:

Factory Method Pattern

The Factory Method pattern is a creational pattern that defines an interface for creating an object, but lets subclasses decide which class to instantiate. This pattern is useful when you want to create a family of related objects, but you don't want to specify the concrete class of each object.

For example, you could use the Factory Method pattern to create a family of animals. The abstract class Animal could define an interface for creating animals, and subclasses could define concrete classes for specific animals, such as Dog, Cat, and Bird.

Builder Pattern

The Builder pattern is a creational pattern that separates the construction of a complex object from its representation. This pattern is useful when you want to create a complex object that can be configured in different ways.

For example, you could use the Builder pattern to create a car. The abstract class Car could define an interface for creating cars, and subclasses could define concrete classes for specific types of cars, such as Sedan, SUV, and Convertible.

Prototype Pattern

The Prototype pattern is a creational pattern that creates objects by cloning an existing object. This pattern is useful when you want to create objects that are similar to each other, but with slight variations.

For example, you could use the Prototype pattern to create a family of animals. The abstract class Animal could define an interface for creating animals, and subclasses could define concrete classes for specific animals, such as Dog, Cat, and Bird. Each subclass could then be instantiated with a unique set of attributes.

These are just a few of the many creational patterns that are available. By understanding these patterns, you can create more flexible and reusable code.

Summary

Creational patterns are a powerful tool for creating objects in a flexible and reusable way. By understanding these patterns, you can improve the design of your code and make it more maintainable.

Next Up: Structural Patterns

Structural patterns are design patterns that deal with the organization of classes and objects. They provide a way to create relationships between objects in a manner that is flexible and reusable.

Structural Patterns

Now that we've covered creational patterns, let's move on to structural patterns.

Structural patterns deal with the organization of classes and objects. They provide a way to create relationships between objects in a manner that is flexible and reusable.

Some of the most common structural patterns include:

  • Adapter Pattern: Allows objects with incompatible interfaces to work together.

  • Facade Pattern: Provides a simplified interface to a complex system.

  • Singleton Pattern: Ensures that only one instance of a class is created.

These are just a few of the many structural patterns that are available. By understanding these patterns, you can improve the design of your code and make it more maintainable.

So, what are you waiting for? Start learning about structural patterns today! (We'll cover Behavioral Patterns next, so stay tuned!)

Behavioral Patterns

Behavioral patterns focus on how objects communicate and interact with each other. They aim to improve code flexibility, reusability, and maintainability.

Observer Pattern

Lets multiple objects (observers) monitor and respond to changes in a single object (subject).

// Subject
class Subject {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  notifyObservers() {
    this.observers.forEach((observer) => observer.update());
  }
}

// Observer class Observer { constructor(name) { this.name = name; } update() { console.log(${this.name} has been notified!); } }

// Usage const subject = new Subject(); const observer1 = new Observer("Observer 1"); const observer2 = new Observer("Observer 2"); subject.addObserver(observer1); subject.addObserver(observer2); subject.notifyObservers(); // Output: Observer 1 has been notified! Observer 2 has been notified!

Mediator Pattern

Facilitates communication between a group of objects that depend on each other in a complex way, without the need for direct dependencies.

// Mediator
class Mediator {
  constructor() {
    this.colleagues = [];
  }
  addColleague(colleague) {
    this.colleagues.push(colleague);
  }
  send(sender, message) {
    this.colleagues.forEach((colleague) => {
      if (colleague !== sender) {
        colleague.receive(sender, message);
      }
    });
  }
}

// Colleague class Colleague { constructor(name, mediator) { this.name = name; this.mediator = mediator; } send(message) { this.mediator.send(this, message); } receive(sender, message) { console.log(${this.name} received: ${message} from ${sender.name}); } }

// Usage const mediator = new Mediator(); const colleagueA = new Colleague("Colleague A", mediator); const colleagueB = new Colleague("Colleague B", mediator); mediator.addColleague(colleagueA); mediator.addColleague(colleagueB); colleagueA.send("Hello from A"); // Output: Colleague B received: Hello from A from Colleague A

Want to learn about the Adapter Pattern? Stay tuned for our next section!

Adapter Pattern

Definition: The Adapter Pattern allows objects with incompatible interfaces to work together.

Practical Way to Remember:

  • Imagine a round peg and a square hole. The Adapter acts as a bridge, making the round peg fit into the square hole.

Key Points:

  • Compatibility Issue: Objects with different interfaces can't communicate or interact directly.
  • Adapter Role: The Adapter provides an interface that matches the target object's expected interface.
  • Bridge: The Adapter sits between the incompatible objects, translating requests and responses.

Benefits:

  • Increased Flexibility: Allows objects from different sources to interact without modifying their code.
  • Enhanced Reusability: Adapters can be reused to connect objects with varying interfaces.
  • Loose Coupling: Reduces dependencies between objects, making the system more maintainable.

Example in JavaScript:

// Target Interface
interface Plug {
  connect();
}

// Adaptee class RoundPlug { connect() { console.log("Round plug connected."); } }

// Adapter class SquarePlugAdapter implements Plug { private roundPlug: RoundPlug;

constructor(roundPlug: RoundPlug) { this.roundPlug = roundPlug; }

connect() { console.log("Adapter: Converting round plug to square."); this.roundPlug.connect(); } }

// Usage const roundPlug = new RoundPlug(); const adapter = new SquarePlugAdapter(roundPlug); adapter.connect(); // Output: Adapter: Converting round plug to square. // Round plug connected.

Conclusion:

The Adapter Pattern provides a versatile way to connect objects with mismatched interfaces, making them work together seamlessly.

Stay tuned for the next section: Facade Pattern!

Facade Pattern

The Facade Pattern provides a simplified interface to a complex system. It hides the intricacies of the system, making it easier to use.

Practical Way to Remember:

Imagine you have a complex home entertainment system with multiple devices connected, such as a TV, DVD player, and sound system. Each device has its own remote control, which can be confusing to use.

The Facade Pattern acts as a universal remote control that simplifies the interaction with the system. You only need to know how to use the universal remote to control all the devices, rather than learning the individual remotes for each device.

Key Points:

  • Hide Complexity: The Facade hides the underlying complexity of the system, exposing only a simplified interface.
  • Single Point of Access: It provides a single point of entry for interacting with the system, reducing the number of connections needed.
  • Encapsulation: It encapsulates the complex dependencies and interactions between the internal components of the system.

Benefits:

  • Simplified Usage: Makes it easier for clients to use the system without having to understand its intricate details.
  • Reduced Coupling: Loosely couples the client from the underlying system, making it easier to make changes to the system without affecting the client.
  • Improved Maintainability: Centralizes the logic for interacting with the system, making it easier to maintain and update.

Exam

Singleton Pattern

The Singleton Pattern is a creational design pattern that ensures that only one instance of a class is created.

Benefits

  • Single point of access: Provides a global point of access to the object.
  • Controlled access: Restricts the creation of multiple instances, preventing unintended object proliferation.
  • Memory efficiency: Saves memory by having only one instance of the object.

Implementation

To implement the Singleton Pattern in JavaScript, we can use the following steps:

  1. Create a private static variable to store the single instance.
  2. Create a public static method to access the instance.
  3. Check if the instance exists. If not, create it and store it in the private variable.

Code Example

class Singleton {
  private static instance;

static getInstance() { if (!this.instance) { this.instance = new Singleton(); } return this.instance; } }

const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true

Practical Use Case

Consider a scenario where you have a configuration manager that loads settings from a database. If multiple instances of this manager are created, they may load the same settings multiple times, wasting time and resources. By using the Singleton Pattern, you ensure that only one instance of the manager exists, which loads the settings once and provides them to all other parts of the application. This leads to improved performance and efficiency.

Next Up: Factory Method Pattern

The Factory Method Pattern provides a way to create objects without specifying the concrete class of the object. This gives you more flexibility in creating objects and allows you to decouple the creation process from the concrete classes. We'll explore this pattern in detail in the next section.

Factory Method Pattern

Let's dive into the Factory Method Pattern, which allows us to customize the way objects are created without directly specifying the class of the object.

Practical Way to Remember: Picture an artist's palette with different types of paintbrushes. Instead of choosing a specific brush type, you can use a factory method to create the brush you need based on the task you're doing.

Key Points:

  • Decoupling Object Creation: Factory methods separate the process of creating an object from the actual class of the object, providing flexibility and control.
  • Common Interface: The factory method returns an object that implements a common interface, ensuring that clients can work with the object regardless of its concrete class.
  • Centralized Creation: Factory methods centralize the logic for creating objects, making it easier to manage and modify the creation process.

Benefits:

  • Code Reusability: Factory methods allow you to reuse creation logic across different parts of your code.
  • Extensibility: New product variations can be easily added without modifying the Factory method itself.
  • Encapsulation: Factory methods hide the implementation details of object creation, protecting the system from changes in the underlying classes.

Example in JavaScript:

// Define an interface for all products
interface Product {
  createProduct(): string;
}

// Concrete product classes class ProductA implements Product { createProduct() { return 'Product A created.'; } }

class ProductB implements Product { createProduct() { return 'Product B created.'; } }

// Factory method class class Factory { createProduct(type: string): Product { switch (type) { case 'A': return new ProductA(); case 'B': return new ProductB(); default: throw new Error(Unknown product type: ${type}); } } }

// Usage const factory = new Factory(); const productA = factory.createProduct('A'); const productB = factory.createProduct('B');

console.log(productA.createProduct()); // Output: Product A created. console.log(productB.createProduct()); // Output: Product B created.

Conclusion:

The Factory Method Pattern provides a flexible way to control the creation of objects, allowing you to easily extend and modify the system without affecting the client code.

Coming Up Next: Builder Pattern

Prepare to explore the Builder Pattern, a powerful tool for creating complex objects with multiple steps involved in their construction.

Builder Pattern

Practical Way to Remember:

Imagine building a house. Instead of gathering all the materials and constructing it all at once, you use a builder who assembles the house piece by piece. The Builder Pattern allows you to create complex objects in a similar way.

Key Points:

  • Separate Construction: Decouples object construction from its representation.
  • Customization: Allows you to build different variations of an object using the same builder interface.
  • Step-by-Step Construction: Breaks down the object construction into a series of manageable steps.

Benefits:

  • Enhanced Flexibility: Changes in construction logic can be easily implemented without affecting the builder interface.
  • Reusability: Builders can be reused to create different types of objects from a common base.
  • Simplified Construction: Makes it easier to build complex objects in a controlled and structured manner.

Example in JavaScript:

Imagine creating a blog post:

class BlogPostBuilder {
  private title: string;
  private body: string;
  private author: string;
  private categories: string[];

setTitle(title: string) { this.title = title; } setBody(body: string) { this.body = body; } setAuthor(author: string) { this.author = author; } addCategory(category: string) { this.categories.push(category); }

build() { return { title: this.title, body: this.body, author: this.author, categories: this.categories }; } }

const builder = new BlogPostBuilder(); builder.setTitle('JavaScript Builder Pattern'); builder.setBody('...some content...'); builder.setAuthor('Afzal'); builder.addCategory('Design Patterns'); const post = builder.build();

console.log(post); // Output: { title: '...', body: '...', author: '...', categories: ['Design Patterns'] }

Next Up: Prototype Pattern

Dive into the Prototype Pattern, where you can create new objects by cloning existing ones without having to recreate them from scratch. Get ready for more exciting design patterns!

Prototype Pattern

What is the Prototype Pattern?

Imagine you have a blueprint for building a car. With this blueprint, you can create as many cars as you want, each with the same design and specifications. The Prototype Pattern in JavaScript works in a similar way, allowing you to create new objects by cloning existing ones.

Key Points:

  • Cloning Existing Objects: Instead of creating new objects from scratch, you can create a clone (copy) of an existing object and modify that clone as needed.
  • Shared Properties: Cloned objects share the same properties as the original object, but they can have different values for those properties.

Benefits:

  • Performance: Cloning objects is typically faster than creating new ones.
  • Memory Efficiency: Cloning existing objects reuses memory, reducing the overall memory consumption of your application.
  • Code Reusability: You can create new objects with variations without having to rewrite the entire construction logic.

Example in JavaScript:

Let's create a simple Person object and clone it to create a new Person object:

let person1 = {
  name: "John",
  age: 30,
};

let person2 = Object.create(person1); // Clone person1 person2.name = "Mary";

console.log(person2); // Output: { name: 'Mary', age: 30 }

Coming Up Next: Decorator Pattern

The Prototype Pattern provides a convenient way to create new objects based on existing ones. Stay tuned for the next adventure in our design patterns journey - the Decorator Pattern - where you'll learn how to enhance existing objects with additional functionality.

Decorator Pattern

Introduction

The Decorator Pattern is a structural design pattern that allows you to add new functionality to an existing object without modifying its structure. This is achieved by wrapping the existing object in a new object that provides the additional functionality.

Key Points

  • Extends functionality: The Decorator Pattern does not modify the existing object, it adds new functionality to it.
  • Loose coupling: The decorator class has a reference to the original object, but the original object is not aware of the decorator.
  • Multiple decorators: Multiple decorators can be applied to a single object, allowing you to combine different functionalities.

Benefits

  • Flexibility: You can easily add new functionality to existing objects without changing the original code.
  • Maintainability: The original object remains unchanged, making it easier to maintain.
  • Extensibility: New decorators can be created and added to the system as needed.

Example in JavaScript

Let's consider an example where we have a simple Car object with the ability to drive. We want to extend this functionality by adding the ability to fly.

// Original object
class Car {
  drive() {
    console.log("Driving...");
  }
}

// Decorator class FlyingCarDecorator { constructor(car) { this.car = car; }

fly() { console.log("Flying..."); }

drive() { this.car.drive(); console.log("Flying while driving..."); } }

// Usage const car = new Car(); const flyingCar = new FlyingCarDecorator(car);

car.drive(); // Driving... flyingCar.drive(); // Driving... Flying while driving... flyingCar.fly(); // Flying...

In this example, the FlyingCarDecorator adds the ability to fly to the Car object while still preserving its original functionality.

Coming Up Next: Proxy Pattern

The Decorator Pattern is a powerful tool for extending the functionality of existing objects. Next, we will explore the Proxy Pattern, which allows you to provide a surrogate or placeholder for another object to control access to it.

Proxy Pattern

Practical Way to Remember:

Think of a secretary who stands in for a busy executive. The secretary receives requests, handles them on the executive's behalf, and forwards important ones to the executive. Similarly, a proxy object acts as an intermediary between a client and a target object, controlling access and functionality.

Key Points:

  • Representation: A proxy object represents another object, providing a controlled interface to it.
  • Virtual Proxy: A proxy that creates the target object only when necessary, optimizing performance.
  • Protection Proxy: A proxy that restricts access to the target object based on specific conditions or privileges.
  • Remote Proxy: A proxy that allows access to a target object located on a different network or system.

Benefits:

  • Controlled Access: Limits access to protected or sensitive objects.
  • Performance Enhancement: Optimizes performance by delaying the creation of expensive objects.
  • Extensibility: Allows you to add new functionality to existing objects without modifying their code.

Example in JavaScript:

class User {
  constructor(name) { this.name = name; }
  getName() { return this.name; }
}

class UserProxy { constructor(user) { this.user = user; } getName() { if (this.user.name !== "admin") { return "Access denied"; } return this.user.getName(); } }

const user = new User("John"); const proxy = new UserProxy(user); console.log(proxy.getName()); // John

const admin = new User("Admin"); const adminProxy = new UserProxy(admin); console.log(adminProxy.getName()); // Access denied

Next Up: Observer Pattern

Having explored the Proxy Pattern, let's delve into the Observer Pattern in the next section, where objects can subscribe to and listen for changes in other objects. Brace yourself for an exciting exploration of event-driven programming!

Observer Pattern

What is the Observer Pattern?

Think of it as a party where you have guests (observers) listening attentively to a host (subject). When the host announces something, all the guests get notified instantly. Similarly, in the Observer Pattern, objects can register as observers to monitor changes in a subject object.

Key Points:

  • Events and Listeners: Observers listen for specific events generated by the subject.
  • Loose Coupling: Observers are not directly tied to the subject, allowing for flexible event handling.
  • Decoupled Updates: Subject updates are decoupled from the logic of the observers, making it easier to add or remove observers.

Benefits:

  • Event-Driven Programming: Facilitates a reactive, event-driven approach to handling changes.
  • Flexibility: Allows for dynamic addition and removal of observers.
  • Extensibility: Easily extendable, allowing for the creation of new observers to handle different events.

Example in JavaScript:

class Subject {
  constructor() {
    this.observers = [];
  }

addObserver(observer) { this.observers.push(observer); }

removeObserver(observer) { this.observers = this.observers.filter(o => o !== observer); }

notifyObservers(event) { this.observers.forEach(o => o.update(event)); } }

class Observer { constructor(id) { this.id = id; }

update(event) { console.log(Observer ${this.id} notified of event ${event}); } }

Next Up: Mediator Pattern

The Observer Pattern is a powerful mechanism for decoupling event handling. In the next section, we'll explore the Mediator Pattern, which introduces an intermediary to facilitate communication and coordination between multiple objects, enabling more complex event interactions.

Mediator Pattern

Hey there, JavaScript enthusiasts! Let's dive into one of the essential design patterns in our journey - the Mediator Pattern. This pattern will empower us to effectively manage communication and coordination between multiple objects within our codebase.

Key Points

  • Central Hub: The Mediator acts as a central hub that orchestrates interactions between different objects.
  • Decoupling: Objects communicate through the Mediator, eliminating direct dependencies and simplifying code maintenance.
  • Centralized Control: The Mediator has a comprehensive view of the system, allowing it to coordinate complex interactions efficiently.

Benefits

  • Simplified Communication: Reduced complexity in managing communication channels, making code more manageable and scalable.
  • Encapsulated Interactions: Changes in communication logic are localized to the Mediator, isolating them from individual objects.
  • Flexibility: Easily add or remove objects from the system without affecting the overall structure.

Example in JavaScript

Consider a chat application where multiple users can send messages to each other. Using the Mediator Pattern, we can create a ChatMediator that acts as the central hub for message exchange.

// ChatMediator
class ChatMediator {
  constructor() {
    this.users = [];
  }

addUser(user) { this.users.push(user); }

removeUser(user) { this.users = this.users.filter((u) => u !== user); }

sendMessage(from, to, message) { const receiver = this.users.find((u) => u.name === to); receiver.receiveMessage(from, message); } }

// User class User { constructor(name, mediator) { this.name = name; this.mediator = mediator; }

sendMessage(to, message) { this.mediator.sendMessage(this.name, to, message); }

receiveMessage(from, message) { console.log(User ${this.name} received message from ${from}: ${message}); } }

// Usage const mediator = new ChatMediator(); const user1 = new User("John", mediator); const user2 = new User("Mary", mediator);

mediator.addUser(user1); mediator.addUser(user2);

user1.sendMessage("Mary", "Hello!"); user2.sendMessage("John", "Hi John!");

Next Up: Command Pattern

The Mediator Pattern enables us to manage intricate interactions between objects. In the following section, we will venture into the Command Pattern, which empowers us to encapsulate actions into discrete objects, providing greater flexibility and control over the execution of tasks.

Command Pattern

What's the Command Pattern?

Imagine you're a chef who wants to delegate tasks to your assistants. You write down each task on a slip of paper, known as a "command." This way, you can instruct your assistants to perform various actions without needing to repeat yourself. In the Command Pattern, objects represent commands, providing flexibility and control over task execution.

Key Points:

  • Encapsulation: Each command encapsulates a specific action, making the code more readable and maintainable.
  • Decoupling: Objects requesting commands are decoupled from the objects executing them, enabling flexibility.
  • Undo/Redo: Commands can be easily stored and replayed, allowing for easy undo/redo functionality.

Benefits:

  • Flexibility: You can add, change, or remove commands without affecting the rest of the system.
  • Simplicity: Complex tasks can be broken down into smaller, manageable commands.
  • Testing: Individual commands are easier to test, improving overall code quality.

Example in JavaScript:

Consider a task manager application. We can use the Command Pattern to create commands for adding, deleting, and updating tasks.

class AddTaskCommand {
  constructor(task) {
    this.task = task;
  }

execute(taskManager) { taskManager.addTask(this.task); } }

class DeleteTaskCommand { constructor(taskId) { this.taskId = taskId; }

execute(taskManager) { taskManager.deleteTask(this.taskId); } }

const taskManager = new TaskManager(); const addTaskCommand = new AddTaskCommand("Buy groceries"); const deleteTaskCommand = new DeleteTaskCommand(1);

addTaskCommand.execute(taskManager); deleteTaskCommand.execute(taskManager);

console.log(taskManager.tasks); // [{"task": "Buy groceries"}]

Next Up: Strategy Pattern

The Command Pattern allows us to manage task execution with flexibility. In the next section, we'll explore the Strategy Pattern, which provides a way to change the behavior of algorithms at runtime, enhancing code adaptability and reusability.

Strategy Pattern

Picture this: you're a general strategizing a battle. You might have different strategies for attacking, defending, or retreating. The Strategy Pattern lets you switch between these strategies on the fly, without changing the underlying code.

Key Points:

  • Strategy Interface: Defines the common interface for different strategies.
  • Concrete Strategies: Implementations of the strategy interface, providing specific behaviors.
  • Context: The object that uses the strategy and can switch between them.

Benefits:

  • Algorithm Independence: Algorithms can be changed without modifying the context.
  • Code Reusability: Strategies can be reused across different contexts.
  • Extensibility: New strategies can be added easily without affecting existing code.

Example in JavaScript:

Let's create a simple game where a character can attack with different weapons.

class Character {
  constructor(weapon) {
    this.weapon = weapon;
  }

attack() { this.weapon.attack(); } }

class Sword { attack() { console.log("Slash!"); } }

class Axe { attack() { console.log("Cleave!"); } }

const character = new Character(new Sword()); character.attack(); // Slash! character.weapon = new Axe(); character.attack(); // Cleave!

Next Up: Template Method Pattern

With the Strategy Pattern, we can dynamically change algorithms. But what if we want to define a fixed sequence of steps with optional variations? That's where the Template Method Pattern steps in...

Template Method Pattern

Greetings, JavaScript enthusiasts! Let's delve into the Template Method Pattern, a powerful design pattern that structures code around a fixed sequence of operations with optional variations. Imagine you're baking a cake; although the overall process remains the same, each recipe might have its unique twists and tweaks. That's the essence of this pattern!

Key Features

  • Template: Outlines the general steps of the process.
  • Concrete Classes: Implement the steps of the template, providing variations where needed.

Benefits

  • Uniform Interface: Ensures consistent flow and structure across different implementations.
  • Code Reusability: Avoids code duplication by extracting common steps into the template.
  • Extensibility: Allows for easy customization by overriding specific steps in concrete classes.

Example in JavaScript

Let's create a game where characters can perform different types of attacks based on their class.

class Character {
  constructor() { }

attack() { this.preAttack();

// Attack logic specific to the subclass
this.performAttack();

this.postAttack();

}

preAttack() { } performAttack() { } postAttack() { } }

class Warrior extends Character { performAttack() { console.log("Swinging sword!"); } }

class Mage extends Character { performAttack() { console.log("Casting fireball!"); }

preAttack() { console.log("Chanting incantation..."); }

postAttack() { console.log("Meditating to regain mana..."); } }

In this example, the Character class is the template, defining the overall attack flow. The Warrior and Mage classes extend it, providing their specific attack implementations while leaving the common steps to the Character class.

Next Up: State Pattern

The Template Method Pattern helps organize code around a fixed sequence. In the next section, we'll explore the State Pattern, which allows objects to change their behavior based on their internal state. Stay tuned, as this pattern is essential for modeling real-world scenarios where objects undergo dynamic changes!

State Pattern

Greetings, JavaScript enthusiasts! In this tutorial, we'll embark on a journey to understand the State Pattern, a powerful design pattern that enables objects to alter their behavior based on their internal state. Think of it as a versatile actor who can seamlessly transform into different roles, each with its unique set of actions and reactions!

Key Points

  • State Interface: Defines the common interface for all possible states of an object.
  • Concrete States: Implementations of the state interface, representing specific behaviors.
  • Context: The object that holds the current state and can transition between states.

Benefits

  • Encapsulated Behavior: Isolates state-specific behavior, making code more modular and easier to maintain.
  • Dynamic Behavior: Objects can change their behavior on the fly, responding to changes in their internal state.
  • State Tracking: Provides a clear and structured way to track and manage the state of objects.

Example in JavaScript

Let's create a simple vending machine that can dispense different products based on the coins inserted.

class VendingMachine {
  constructor() {
    this.state = new NoCoinState(); // Initial state
  }

insertCoin(coin) { this.state.insertCoin(this, coin); }

pressButton() { this.state.pressButton(this); }

setState(state) { this.state = state; } }

class NoCoinState { insertCoin(vendingMachine, coin) { vendingMachine.setState(new HasCoinState(coin)); }

pressButton(vendingMachine) { console.log("Please insert a coin first."); } }

class HasCoinState { constructor(coin) { this.coin = coin; }

insertCoin(vendingMachine, coin) { console.log("Sorry, you can only insert one coin at a time."); }

pressButton(vendingMachine) { vendingMachine.setState(new DispenseState(this.coin)); } }

class DispenseState { constructor(coin) { this.coin = coin; }

insertCoin(vendingMachine, coin) { console.log("Sorry, you already pressed the button."); }

pressButton(vendingMachine) { console.log("Sorry, you already pressed the button."); } }

In this example, the VendingMachine class is the context, which can be in different states (NoCoinState, HasCoinState, and DispenseState). The state objects handle coin insertion and button press events based on the current state of the machine.

Share Button