The intent of decorator design pattern, as described in Design Patterns by the Gang of Four is to attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

To extend the functionality of any object, we use subclassing (read inheritance) to add that new function to the subclass while keeping the rest of inherited functionalities from its superclass. But it is kind of impossible to extend a single object this way because this new function will be applied to the whole class. This is where the Decorator Pattern comes in handy. We can extend the functionality of a single object without affecting any other instances of the same class. Essentially with decorator, we are wrapping the existing object with additional functionality.

Let’s imagine a House class with a rent method that returns 10000.

class House
  def rent
    5000
  end
  def has_pool?
    false
  end
end

Now we need to represent house with more bedrooms, the rent goes up by 5000. Our normal approach would be to create a TwoBhkHouse subclass that adds 5000 to the rent.

class TwoBhkHouse < OneBhkHouse
  def rent
    15000
  end
end

We could also have an ThreeBhkHouse which adds another 5000 to our TwoBhkHouse. Now there could be houses with 4bhk and 5bhk as well. If were to continue in the above fashion, we would need to add FourBhkHouse and FiveBhkHouse subclasses as well, giving us a total of 5 classes.

Using the Decorator pattern -> We can start with a decorator called LargeHouse that can be seen as a wrapper around a House object.

class LargeHouse
  def initialize(house)
    @house = house
  end

  def rent
    @house.rent + 5000
  end
end

Extra bedroom houses can now be created by using this wrapper twice on a House object.

  house = House.new
  two_bhk_house = LargeHouse.new(house)
  three_bhk_house = LargeHouse.new(two_bhk_house)
  four_bhk_house = LargeHouse.new(three_bhk_house)

But our implementation has one disadvantage: the has_pool? method will no longer be available after decorator has been applied on it. For solving this problem, we can use SimpleDelegator class in Ruby. We can implement a HouseDecorator base class from which all our decorator classes can inherit from.

# SimpleDelegator makes all the methods of the house object available on the decorated objects.
class HouseDecorator < SimpleDelegator
  def initialize(house)
    @house = house
    super
  end
  def rent
    .....
  end
end

References: Decorator Pattern, SimpleDelegator