Design Patterns: Decorator Pattern

The best way to decorate.

·

3 min read

The Decorator pattern is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. This pattern is suitable for projects where there is a need to add or modify the behavior of objects at runtime, without affecting other objects or the overall system.

One example of a project where the Decorator pattern is useful is the development of a text editor that supports multiple formatting options. Different formatting options, such as bold, italic, underline, or font size, can be applied to text in different combinations. By using the Decorator pattern, the text editor can be designed to add or modify the formatting options dynamically, without affecting other parts of the text or the overall document. This can help to improve the flexibility and usability of the text editor and make it easier to add new formatting options in the future.

Another example of a project where the Decorator pattern is useful is the development of a shopping cart system for an e-commerce website. Different products may have different pricing options, such as discounts, promotions, or taxes. By using the Decorator pattern, the shopping cart system can be designed to add or modify the pricing options dynamically, without affecting the other products in the cart or the overall checkout process. This can help to improve the accuracy and transparency of the pricing system and make it easier to add or modify pricing options in the future.

The Decorator pattern should preferably be used in projects where there is a need to add or modify the behavior of objects at runtime, without affecting other objects or the overall system. Reducing the coupling between an object and its behavior can enhance code flexibility and maintainability, enabling easier addition or modification of behaviors in the future.

Let's take a quick look at an application utilizing this pattern.

// Payment Strategy
class PaymentStrategy {
  constructor(paymentMethod) {
    this.paymentMethod = paymentMethod;
  }

  pay(amount) {
    throw new Error('pay method must be implemented');
  }
}

// Payment Strategies
class CreditCardStrategy extends PaymentStrategy {
  constructor() {
    super('Credit Card');
  }

  pay(amount) {
    console.log(`Paying ${amount} with ${this.paymentMethod}...`);
    // Implement credit card payment logic here...
  }
}

class PayPalStrategy extends PaymentStrategy {
  constructor() {
    super('PayPal');
  }

  pay(amount) {
    console.log(`Paying ${amount} with ${this.paymentMethod}...`);
    // Implement PayPal payment logic here...
  }
}

class ApplePayStrategy extends PaymentStrategy {
  constructor() {
    super('Apple Pay');
  }

  pay(amount) {
    console.log(`Paying ${amount} with ${this.paymentMethod}...`);
    // Implement Apple Pay payment logic here...
  }
}

// Payment Processor
class PaymentProcessor {
  constructor(paymentStrategy) {
    this.paymentStrategy = paymentStrategy;
  }

  processPayment(amount) {
    this.paymentStrategy.pay(amount);
  }
}

// Usage
const paymentMethod = 'Credit Card';
const amount = 100;

let paymentStrategy;

switch (paymentMethod) {
  case 'Credit Card':
    paymentStrategy = new CreditCardStrategy();
    break;
  case 'PayPal':
    paymentStrategy = new PayPalStrategy();
    break;
  case 'Apple Pay':
    paymentStrategy = new ApplePayStrategy();
    break;
  default:
    throw new Error('Invalid payment method');
}

const paymentProcessor = new PaymentProcessor(paymentStrategy);
paymentProcessor.processPayment(amount);
// Output: Paying 100 with Credit Card...

In the above code, we define a PaymentStrategy class that provides a pay method to process the payment based on the selected payment method. We also define three payment strategy classes: CreditCardStrategy, PayPalStrategy, and ApplePayStrategy, which implement the same pay method to process the payment based on the selected payment method.

We then define a PaymentProcessor class that takes a PaymentStrategy instance in its constructor, and provides a processPayment method to trigger the payment processing using the selected payment strategy.

In the usage example, we create a new instance of the appropriate payment strategy class based on the paymentMethod parameter, and pass it to a new instance of the PaymentProcessor class. We can then call the processPayment method on the PaymentProcessor instance to process the payment using the selected payment strategy.

Did you find this article valuable?

Support Clint by becoming a sponsor. Any amount is appreciated!