Decorator Pattern in TypeScript — ✨ Structural Design Pattern #3

365kim
3 min readJun 11, 2023

--

Photo by Kira auf der Heide on Unsplash

🙋🏻‍♀️ What is the Decorator Pattern?

The Decorator Pattern is a structural design approach that provides a way to extend the functionality of an object dynamically without subclassing or modifying its underlying structure.

Decorator objects enhance the decorated object’s behavior by adding additional logic by simply wrapping the object, so it is sometimes called Wrap Pattern as well. Of course, the decorator needs to conform to the same interface as the object it’s wrapping.

The Decorator Pattern follows the principle of composition over inheritance, promoting flexibility and code reuse. Instead of creating multiple subclasses with different combinations, decorators can be stacked or removed at runtime, allowing for dynamic modification of an object’s functionality.

✨ Simple TypeScript Example

Let’s say you start a small coffee business. Here, you have a component interface Coffee that defines the common operations for coffee objects, such as getCost() and getDescription().

interface Coffee {
getCost(): number
getDescription(): string
}

SimpleCoffee is a concrete class that implements the Coffee interface. It represents the base coffee without any additional customization options.

class SimpleCoffee implements Coffee {
getCost(): number {
return 4_100
}

getDescription(): string {
return 'Single Origin Coffee'
}
}
// Client Code
const coffee: Coffee = new SimpleCoffee()

console.log(coffee.getDescription()) // Single Origin Coffee
console.log(coffee.getCost()) // 4100

Now, your customer is interested in having additional options such as adding milk or syrup, rather than being limited to simple coffee.

You prepare an abstract decorator class CoffeeDecorator. This has a reference to the Coffee and delegates getCost() and getDescription() to the decorated coffee.

// Decorator class
class CoffeeDecorator implements Coffee {
protected decoratedCoffee: Coffee

constructor(decoratedCoffee: Coffee) {
this.decoratedCoffee = decoratedCoffee
}

getCost(): number {
return this.decoratedCoffee.getCost() // delegation
}

getDescription(): string {
return this.decoratedCoffee.getDescription() // delegation
}
}

The Milk and HazelnutSyrup classes are concrete decorators that extend the CoffeeDecorator class. They add specific functionalities to the decorated coffee by overriding the getCost() and getDescription().

// Concrete decorators
class Milk extends CoffeeDecorator {
constructor(decoratedCoffee: Coffee) {
super(decoratedCoffee)
}

getCost(): number {
return super.getCost() + 500
}

getDescription(): string {
return super.getDescription() + ', Milk'
}
}

class HazelnutSyrup extends CoffeeDecorator {
constructor(decoratedCoffee: Coffee) {
super(decoratedCoffee)
}

getCost(): number {
return super.getCost() + 300
}

getDescription(): string {
return super.getDescription() + ', Hazelnut Syrup'
}
}

In the client code, the decorators are applied in a chain, and each decorator modifies the cost and description of the decorated coffee.

// Client Code
const coffee: Coffee = new SimpleCoffee()
const cafeLatte: Coffee = new Milk(coffee)
const hazelnutLatte: Coffee = new HazelnutSyrup(cafeLatte)

console.log(hazelnutLatte.getDescription()) // Single Origin Coffee, Milk, Hazelnut Syrup
console.log(hazelnutLatte.getCost()) // 4900

🧑🏻‍💻 Use it or Avoid it

When to use it

The Decorator Pattern is useful if you need the ability to add or remove behaviors dynamically at runtime, or if you need fine-grained control over its functionalities.

Also, it’s a good strategy to promote modularity. You can create a set of decorators that can be combined in various ways. This modular approach makes it easier to maintain and extend the codebase.

When to avoid it

It’s noteworthy that decorators might result in a deep nesting of wrapped objects, so it may negatively impact performance and readability. Therefore, if the functionality you need to add is quite simple, a simpler approach like subclassing or direct modification might be more appropriate.

--

--

365kim
365kim

Written by 365kim

Web Front-End Developer who believes Every Day Counts!

No responses yet