The Decorator Pattern is a structural design pattern that 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 particularly useful when you want to add responsibilities to an object in a flexible and reusable way.
In this tutorial, we'll explore how the Decorator Pattern works, its benefits, and provide practical examples to illustrate its usage.
The core idea behind the Decorator Pattern is to wrap an object with another object that adds new functionality. This wrapping can be done dynamically at runtime, allowing for flexible behavior modification without altering the original object's structure or code.
Component object.Let's dive into some practical examples to understand how the Decorator Pattern works in action.
Suppose we have a simple coffee shop application where we can order different types of coffee. We want to add various condiments (like sugar, milk) to our coffee without altering the base coffee class.
// Component interface
class Coffee {
cost() {
return 5; // Base price for coffee
}
description() {
return "Coffee";
}
}
// Concrete Component
class Espresso extends Coffee {
constructor() {
super();
}
cost() {
return 7; // Espresso has a higher base price
}
description() {
return "Espresso";
}
}
// Decorator
class CondimentDecorator extends Coffee {
constructor(coffee) {
super();
this._coffee = coffee;
}
cost() {
return this._coffee.cost();
}
description() {
return this._coffee.description();
}
}
// Concrete Decorators
class Milk extends CondimentDecorator {
constructor(coffee) {
super(coffee);
}
cost() {
return this._coffee.cost() + 2; // Adding milk increases the price by $2
}
description() {
return `${this._coffee.description()}, Milk`;
}
}
class Sugar extends CondimentDecorator {
constructor(coffee) {
super(coffee);
}
cost() {
return this._coffee.cost() + 1; // Adding sugar increases the price by $1
}
description() {
return `${this._coffee.description()}, Sugar`;
}
}
### Usage
```jsx
const espresso = new Espresso();
console.log(espresso.description()); // Output: Espresso
console.log(`Cost: $${espresso.cost()}`); // Output: Cost: $7
const milkEspresso = new Milk(new Espresso());
console.log(milkEspresso.description()); // Output: Espresso, Milk
console.log(`Cost: $${milkEspresso.cost()}`); // Output: Cost: $9
const sugarMilkEspresso = new Sugar(new Milk(new Espresso()));
console.log(sugarMilkEspresso.description()); // Output: Espresso, Milk, Sugar
console.log(`Cost: $${sugarMilkEspresso.cost()}`); // Output: Cost: $10
Another common use case for the Decorator Pattern is adding logging or other cross-cutting concerns to functions.
// Component interface (function)
function simpleFunction() {
console.log("Executing simple function");
}
// Decorator
function logDecorator(func) {
return function(...args) {
console.log(`Before calling ${func.name}`);
const result = func.apply(this, args);
console.log(`After calling ${func.name}`);
return result;
};
}
// Usage
const decoratedFunction = logDecorator(simpleFunction);
decoratedFunction();
<OutputBlock>
{`Before calling simpleFunction
Executing simple function
After calling simpleFunction`}
</OutputBlock>
Now that you have a good understanding of the Decorator Pattern, you might want to explore other structural patterns like the Facade Pattern. The Facade Pattern simplifies complex subsystems by providing a unified interface.
Stay tuned for more design pattern tutorials on codingstuff.io!
Info