Skip to main content

Command Palette

Search for a command to run...

Decorator Design Pattern

Published
8 min readView as Markdown

The Decorator Design Pattern is a structural design pattern used to dynamically extend the functionality of objects without modifying their original implementation. It allows wrapping objects with additional behaviour at runtime.

Key Characteristics

  1. Extends functionality dynamically without modifying the base class.

  2. Follows the open/closed principle - new functionality can be added without modifying existing code.

  3. Encapsulates behaviour in layers, making the code more modular and flexible.

  4. Supports multiple decorators that can be applied in any order.

We will discuss a use-case API Request Handling using Typescript code to understand this design pattern.

We need to send a POST request using fetch(), but we want to add additional functionalities like:

  • Logging the request and response.

  • Retrying failed requests a few times before giving up.

  • Adding a timeout to cancel long-running requests.

Instead of modifying the core API request class, we will use decorators to wrap and enhance its behaviour.

Step 1 : Define the Interface

We will first define the interface that all request handlers (including decorators) must follow.

interface APIRequestHandler {
  sendRequest(url: string, data: any): Promise<string>;
}

Every API request handler must implement the method sendRequest() which takes the URL and data, then returns a Promise<string>

Step 2 : Implement the Base Request Handler

This class performs the actual HTTP request using fetch()

class BasicAPIRequestHandler implements APIRequestHandler {
   async sendRequest(url: string, data: any): Promise<string> {
        try {
            const response = await fetch(url, {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify(data),
            });

            if (!response.ok) {
              throw new Error(`HTTP error! Status: ${response.status}`);
            }

            return await response.text();
          } catch (error) {
            if (error instanceof Error) {
              throw new Error(`Request failed: ${error.message}`);
            } else {
              throw new Error('Request failed with an unknown error');
            }
          }
    }
}
//Sends a POST request to the given URL

//Converts data to a JSON string

//Throws an error if request fails

Step 3 : Implements the Base Decorator class

All decorators will extend this base class. It implements APIRequestHandler and wraps another APIRequestHandler instance.

class APIRequestDecorator implements APIRequestHandler {
    protected apiRequestHandler: APIRequestHandler;

    constructor(apiRequestHandler: APIRequestHandler) {
        this.apiRequestHandler = apiRequestHandler;
    }

    async sendRequest(url: string, data: any