Decorator Design Pattern in R

1. Definition

The Decorator Design Pattern dynamically attaches additional responsibilities to an object. It provides a flexible alternative to subclassing for extending functionality. In essence, a decorator wraps an object and provides additional behavior, while also allowing the original object's methods to be called.

2. Problem Statement

Consider you have a basic coffee drink and you want to be able to add various condiments like milk, sugar, whipped cream, etc. How can you design your system so that adding new condiments or combinations doesn't require modifying the existing code?

3. Solution

Employ the Decorator Pattern. Create a base component (Coffee) and then create decorator components (Milk, Sugar) that will wrap around the base component. Each decorator will add its own behavior (e.g., cost) and can also call the wrapped object's methods.

4. Real-World Use Cases

1. Java's I/O classes use decorators. For example, you can wrap a BufferedReader around a FileReader to extend its capabilities.

2. Adding graphical borders or scroll bars to windows in a GUI toolkit.

3. Middleware in web frameworks where each middleware wraps around the main application, adding functionalities like logging, caching, etc.

5. Implementation Steps

1. Define a component interface.

2. Create the concrete component that implements the component interface.

3. Create abstract decorator class that also implements the component interface.

4. Create concrete decorator classes that extend the abstract decorator, adding new behavior.

6. Implementation in R Programming

# Step 1: Component Interface
Beverage <- function() {
  list(
    cost = function() {
      stop("Abstract method. Implement in concrete components.")
    }
  )
}
# Step 2: Concrete Component
Coffee <- function() {
  drink <- Beverage()
  drink$cost <- function() {
    return(2.00)
  }
  return(drink)
}
# Step 3: Abstract Decorator
CondimentDecorator <- function(beverage) {
  drink <- Beverage()
  drink$beverage <- beverage
  return(drink)
}
# Step 4: Concrete Decorators
Milk <- function(beverage) {
  condiment <- CondimentDecorator(beverage)
  condiment$cost <- function() {
    return(condiment$beverage$cost() + 0.50)
  }
  return(condiment)
}
Sugar <- function(beverage) {
  condiment <- CondimentDecorator(beverage)
  condiment$cost <- function() {
    return(condiment$beverage$cost() + 0.25)
  }
  return(condiment)
}
# Client code
coffee <- Coffee()
coffeeWithMilk <- Milk(coffee)
coffeeWithMilkAndSugar <- Sugar(coffeeWithMilk)
output <- coffeeWithMilkAndSugar$cost()

Output:

2.75

Explanation:

1. We start with a Beverage interface that both the core component Coffee and the decorators (e.g., Milk, Sugar) will implement.

2. Coffee simply returns its cost.

3. Each decorator has a reference to a Beverage object. When its cost method is called, it calls the cost method of the wrapped beverage and adds its own cost.

4. In the client code, we wrap a Coffee with Milk and then wrap the result with Sugar. Calling the cost method of the outermost object gives the total cost.

7. When to use?

Use the Decorator Pattern when:

1. You need to add responsibilities to individual objects dynamically and transparently without affecting other objects.

2. When subclassing is not feasible because it produces an excessive number of subclasses.

3. When you want to keep new functionality separate.

The Decorator pattern offers a more flexible way to add functionality compared to static inheritance.


Comments