Decorator Design Pattern in Ruby

1. Definition

The Decorator Design Pattern lets you add further responsibilities to an object dynamically, without modifying its structure. Decorators provide a flexible alternative to subclassing for extending functionality.

2. Problem Statement

Sometimes we need to add responsibilities to individual objects, not to an entire class. A straightforward way might be to extend the class, but it can become cumbersome and lead to a large number of subclasses which can be hard to maintain.

3. Solution

The Decorator Pattern suggests creating a set of decorator classes that mirror the type of the objects they will augment. You can use these to wrap concrete components with new behaviors.

4. Real-World Use Cases

1. Adding window dressing or borders to graphical windows.

2. Adding responsibilities to UI components dynamically, like scroll bars.

3. Enhancing classes in third-party libraries without altering the original code.

5. Implementation Steps

1. Ensure there's a common interface between your primary object and the decorators.

2. Create concrete implementations of the primary object.

3. For each additional behavior, create a new decorator class mirroring the primary object's interface.

4. Decorators wrap around the primary or other decorator objects, adding their own behavior when methods are invoked.

6. Implementation in Ruby

# Step 1: Common Interface
module Beverage
  def cost
    raise NotImplementedError, 'Subclasses must define `cost`.'
  end
end
# Step 2: Concrete Implementations
class Coffee
  include Beverage
  def cost
    5
  end
end
# Step 3: Decorator Classes
class SugarDecorator
  include Beverage
  def initialize(beverage)
    @beverage = beverage
  end
  def cost
    @beverage.cost + 1
  end
end
class MilkDecorator
  include Beverage
  def initialize(beverage)
    @beverage = beverage
  end
  def cost
    @beverage.cost + 2
  end
end
# Step 4: Client Code
coffee = Coffee.new
sugar_coffee = SugarDecorator.new(coffee)
double_sugar_milk_coffee = MilkDecorator.new(SugarDecorator.new(sugar_coffee))
puts double_sugar_milk_coffee.cost

Output:

8

Explanation:

1. Beverage is the common interface for both the primary object and the decorators.

2. Coffee is a concrete implementation of the Beverage interface.

3. SugarDecorator and MilkDecorator are the decorator classes. They wrap around a Beverage object, adding or altering behaviors.

4. The client code demonstrates how to decorate a Coffee object with additional behaviors using decorators. Each decorator wraps the previously decorated object.

7. When to use?

Use the Decorator Design Pattern when:

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

2. Extending the responsibilities of a class through inheritance isn't feasible due to its inflexibility.

3. You want to keep new responsibilities separate by using individual classes for each feature.


Comments