Article Image
Article Image
read

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,Sugar

In 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())
Blog Logo

Ankur Patel


Published

Image

Ankur અંકુર Patel

Ruby on Rails, iOS and Frontend Web Development follow me on Twitter @AnkurPatel

Back to Overview