Skip to main content

Decorator

The Decorator pattern is a structural design pattern used in object-oriented programming. It allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. This pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.

Here's an overview of the Decorator pattern:

decorator

Decorator Pattern in the Wikipedia

  1. Component: This is the base interface or abstract class defining the operations that can be altered by decorators.

  2. ConcreteComponent: A class implementing the Component interface or inheriting from the abstract class. It defines an object to which additional responsibilities can be attached.

  3. Decorator: An abstract class that implements or inherits from the Component interface and has a reference to a Component object. It can also have additional functionalities, but its main job is to delegate the operation to the component it decorates.

  4. ConcreteDecorator: Classes that extend Decorator classes. Each ConcreteDecorator adds its own behavior either before or after delegating the operation to its Component object.

This pattern is particularly useful when you want to add responsibilities to objects at runtime without affecting other objects of the same class. In programming, this could be used to modify or extend the behavior of an object in a flexible and scalable way.

Example in TypeScript​

// Component
interface Coffee {
cost(): number;
description(): string;
}

// ConcreteComponent
class SimpleCoffee implements Coffee {
cost(): number {
return 10;
}

description(): string {
return 'Simple coffee';
}
}

// Decorator
abstract class CoffeeDecorator implements Coffee {
constructor(protected coffee: Coffee) {}

abstract cost(): number;
abstract description(): string;
}

// ConcreteDecorator
class WithMilk extends CoffeeDecorator {
cost(): number {
return this.coffee.cost() + 2;
}

description(): string {
return `${this.coffee.description()}, milk`;
}
}

// Another ConcreteDecorator
class WithSugar extends CoffeeDecorator {
cost(): number {
return this.coffee.cost() + 1;
}

description(): string {
return `${this.coffee.description()}, sugar`;
}
}

// Usage
let coffee = new SimpleCoffee();
console.log(coffee.description()); // Simple coffee
console.log(coffee.cost()); // 10

coffee = new WithMilk(coffee);
console.log(coffee.description()); // Simple coffee, milk
console.log(coffee.cost()); // 12

coffee = new WithSugar(coffee);
console.log(coffee.description()); // Simple coffee, milk, sugar
console.log(coffee.cost()); // 13

In this example, the SimpleCoffee class can be decorated at runtime with additional features like milk or sugar, each adding to its cost and description. This approach promotes flexible design and helps avoid subclass proliferation for different combinations of features.

Tips for Fullstack Developers​

  • When implementing the Decorator pattern, especially in TypeScript, leverage interfaces and abstract classes to ensure type safety and clear contracts for your components and decorators.
  • Consider using the Decorator pattern for cross-cutting concerns in your application, like logging or authentication, to keep your code modular and maintainable.
  • Always be mindful of the balance between flexibility and complexity. While the Decorator pattern offers high flexibility, it can also introduce complexity, so use it judiciously based on your application's needs.

Higher-Order (HoCs)​

The concept of Higher-Order Components (HOCs) in React is indeed very similar to the Decorator pattern. Both patterns involve wrapping an existing object (or component, in the case of React) with another object (or component) that adds additional behavior or properties.

In React, a Higher-Order Component is a function that takes a component and returns a new component. It's used for reusing component logic. The HOC pattern can be seen as a way of implementing the principles of the Decorator pattern in the context of React components.

Similarities​

  1. Composition: Both patterns use composition over inheritance. In the Decorator pattern, a decorator class wraps the original class. In HOCs, the returned component wraps the passed component.

  2. Extending Behavior: Both add or modify behavior without altering the original object or component. In React, HOCs add features or data to the original component.

  3. Dynamic Usage: Both can be applied dynamically. Decorators can be used to extend objects at runtime, and HOCs can be used to enhance components based on props or application state.

Example of a Higher-Order Component in React​

import React from 'react';

// A simple HOC that adds additional data to the original component
function withExtraInfo(WrappedComponent: React.ComponentType) {
return class extends React.Component {
render() {
const extraInfo = { importantData: 'Some important data' };
return <WrappedComponent {...this.props} extraInfo={extraInfo} />;
}
};
}

// Usage
const MyComponent = (props: any) => <div>{props.extraInfo.importantData}</div>;
const EnhancedComponent = withExtraInfo(MyComponent);

Tips for Fullstack Developers Using HOCs​

  • Ensure HOCs are pure functions: they should not modify the input component, but rather return a new component.
  • Pass unrelated props through to the wrapped component to ensure component isolation and reusability.
  • Be aware of the potential for prop name collisions. Make sure that the HOC doesn’t accidentally overwrite a prop that the wrapped component is expecting.
  • Consider using Hooks in functional components as an alternative to HOCs. They can often achieve similar results with less complexity and better readability.