Decorating a class is another way of extending the functionality of a class instead of subclassing. In Ruby there is an elegant way to decorate a class using SimpleDelegator.
Lets say you have a Coffee class and want to decorate the class by adding a Milk and Sugar version of it without subclassing, then you can do so as such in Ruby:
class Coffee
def get_cost
2.0
end
def ingredients
"Coffee"
end
end
class Milk < SimpleDelegator
def ingredients
super + ",Milk"
end
end
class Sugar < SimpleDelegator
def ingredients
super + ",Sugar"
end
end
plain_coffee = Coffee.new
milk_coffee = Milk.new(plain_coffee)
milk_and_sugar_coffee = Sugar.new(milk_coffee)
puts plain_coffee.ingredients
# => Coffee
puts milk_coffee.ingredients
# => Coffee,Milk
puts milk_and_sugar_coffee.ingredients
# => Coffee,Milk,SugarIn the example above, you have Coffee class which returns Coffee for ingredients but when you decorate the plain_coffee object with Milk decorator then it returns Coffee,Milk for ingredients. And when we decorate the milk_coffee object with Sugar then it returns Coffee,Milk,Sugar for ingredients.
I found decorating objects very easy in Ruby due to SimpleDelegator as it delegates all methods to the object being decorated that is passed in the contrustor of the decorator. It is able to do so using method_missing available in Ruby which SimpleDelegator uses to invoke the same method on the object being decorated. Compare this with writing a similar decorator in language like Swift or Java where you need to write a Decorator class and then subclass the decorator class for Milk and Sugar coffee:
protocol Coffee {
func getIngredients() -> String;
}
class SimpleCoffee : Coffee {
func getIngredients() -> String {
return "Coffee"
}
}
class CoffeeDecorator : Coffee {
private let decoratedCoffee: Coffee
required init(decoratedCoffee: Coffee) {
self.decoratedCoffee = decoratedCoffee
}
func getIngredients() -> String {
return decoratedCoffee.getIngredients()
}
}
final class Milk: CoffeeDecorator {
override func getIngredients() -> String {
return super.getIngredients() + ",Milk"
}
}
final class Sugar: CoffeeDecorator {
override func getIngredients() -> String {
return super.getIngredients() + ",Sugar"
}
}
let plainCoffee = SimpleCoffee()
print(plainCoffee.getIngredients())
let milkCoffee = Milk(decoratedCoffee: plainCoffee)
print(milkCoffee.getIngredients())
let milkSugarCoffee = Sugar(decoratedCoffee: milkCoffee)
print(milkSugarCoffee.getIngredients())