Skip to main content

Introduction To Decorator Pattern

The Decorator design pattern is a structural design pattern that allows you to dynamically add or override behaviour in an existing object without changing its implementation. This pattern is particularly useful when you want to modify the behavior of an object without affecting other objects of the same class.

The Decorator pattern is a good alternative to subclassing when you need to add functionality at runtime. Subclassing can be static and happens at compile time, whereas the Decorator pattern allows behavior to be set dynamically.

Components of the Decorator Pattern

  1. Component: This is the base interface or abstract class, which defines the methods that will be implemented.

  2. ConcreteComponent: This is a class which implements the Component interface.

  3. Decorator: This is also an interface or an abstract class which implements the Component interface.

  4. ConcreteDecorator: This is a class which implements the Decorator. It extends the Decorator to decorate the Component.

Classic Implementation

Here's a simplified example of the decorator pattern in TypeScript:

// Component
interface Coffee {
cost(): number;

description(): string;
}

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

description() {
return "Simple coffee";
}
}

// Decorator
abstract class CoffeeDecorator implements Coffee {
protected coffee: Coffee;

constructor(coffee: Coffee) {
this.coffee = coffee;
}

abstract cost(): number;

abstract description(): string;
}

// ConcreteDecorator
class MilkDecorator extends CoffeeDecorator {
constructor(coffee: Coffee) {
super(coffee);
}

cost() {
return this.coffee.cost() + 2;
}

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

// Usage
let coffee: Coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);

console.log(`Cost: ${coffee.cost()}`); // "Cost: 12"
console.log(`Description: ${coffee.description()}`); // "Description: Simple coffee, with milk"

In this example, Coffee is our Component, SimpleCoffee is our ConcreteComponent, CoffeeDecorator is our Decorator, and MilkDecorator is our ConcreteDecorator. By using the MilkDecorator, we're able to augment a SimpleCoffee with additional functionality (adding milk), and because our MilkDecorator uses the same Coffee interface, it can be used anywhere a Coffee can.

Note that this is a relatively simple usage of the Decorator pattern and things can get complex very quickly when you start creating more decorators and chaining them together.

When To Use Decorator Pattern

The Decorator Pattern is a structural design pattern that allows you to add new behaviors to objects dynamically by placing them inside special wrapper objects. This pattern is typically used in situations where you need to extend the behavior of an object without making code overly complicated.

Here are some situations where you might consider using the Decorator Pattern:

  1. You need to add responsibilities to individual objects dynamically and transparently: That is, without affecting other objects. For example, you have a simple text editor and want to add formatting options like bold, italic, underlining, etc. You could use decorators to wrap the basic editor and add these features independently.

  2. You need to add responsibilities to an object that can be withdrawn later : The Decorator Pattern lets you add and remove responsibilities from an object at runtime.

  3. You want to add a few additional properties to some objects, but not all: Let's say you're building a graphic editor, and only a few objects should have some special properties (like color, border style, etc.). Using the decorator pattern, you can add these properties only to required objects.

  4. Extending class functionality is not a viable option: Sometimes, subclassing is not the best solution, especially when dealing with a large number of independent extensions. The decorator pattern provides an alternative approach where you can build functionality dynamically by combining different decorators.

  5. You want to ensure that the system can be easily extended in the future: Decorators keep the client code simpler than extensive subclassing. The pattern also allows for future extension where new decorator classes could be created and easily integrated into the existing system.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle