Decorator Pattern with Ruby in 8 lines
Wed Sep 06 19:30:00 UTC 2006
The Decorator Pattern is a design pattern that enables you to dynamically wrap behaviour around an existing object at runtime. It is especially useful when an object can have many variables that can be combined in different ways, which in turn affect it’s behaviour.
This small, simple implementation of the decorator pattern in Ruby sums up what I love about the hottest dynamic language on the planet.
I’ll borrow a simple example from the excellent Head First Design Patterns book by Eric Freeman, Elisabeth Freeman, Kathy Sierra, and Bert Bates.
Let’s say you want to calculate the cost of a cup of coffee. You have Coffee class, which implements a
cost() method. For the purposes of this example, let’s just hardcode a value:
class Coffee def cost 2 end end
Great. But what if we want the cost of a coffee with milk? We could have a new class:
class WhiteCoffee def cost 2.4 end end
OK. But now we want cream. And sprinkles. Clearly, creating new classes is going to lead to a huge number of classes in our application. It just isn’t realistic to create classes for different combinations of coffee and extras. It could get worse – what if we have different types of coffee? We would then have to have combinations of extras with each different type of coffee. It just isn’t going to work. Enter the decorator pattern. Here’s the the 8 lines of Ruby mentioned in this entry’s title:
module Decorator def initialize(decorated) @decorated = decorated end
def method_missing(method, *args) args.empty? ? @decorated.send(method) : @decorated.send(method, args) end end
And thats all you need. You can include this into any class you want to act as a decorator. You can then use that decorator as if it was the object it is decorating; by default all messages sent to the decorator are forwarded on to the decorated object. You can then decorate the methods you need to extend:
class Milk include Decorator
def cost @decorated.cost + 0.4 end end
So how does this solve our original problem? The real power of decorators lies in the fact that they can act like the objects they are decorating. By taking this one step further, you can wrap decorators with other decorators as long as they share the same interface. By creating decorators for our different “extras”, we can create coffees using a combination of decorators and get the total cost of the coffee.
class Whip include Decorator
def cost @decorated.cost + 0.2 end end
class Sprinkles include Decorator
def cost @decorated.cost + 0.3 end end
Whip.new(Coffee.new).cost #=> 2.2 Sprinkles.new(Whip.new(Milk.new(Coffee.new))).cost #=> 2.9
Of course, there’s nothing stopping us from making life easier with a few factory methods:
class CoffeeFactory def self.latte SteamedMilk.new(Espresso.new) end
def self.cappuccino Sprinkles.new(Cream.new(Milk.new(Coffee.new))) end end
order = Order.new order.add(Coffee.new) order.add(CoffeeFactory.cappuccino) puts order.total
Due to Ruby’s highly dynamic nature, the Decorator pattern isn’t the only way of extending class functionality at runtime, however I just love how simple it is to implement the pattern in Ruby. For more information on implementing decorators in Ruby, including generic decorators and alternatives to the traditional decorator pattern, see the DecoratorPattern page at the RubyGarden.
Finally, it would be nice to maintain some kind of identity when using decorators. The RubyGarden page above has one approach. Whilst we aren’t using inheritance, it would be nice to retain one of its features:
CoffeeFactory.cappucino.kind_of? Coffee #=> true
I can think of a few ways of extending my Decorator class to retain this functionality, but I’m gonna leave this one to the reader. Over to you.
Update: In typical Ruby-fashion, my 8 lines of code has been beaten by, well, zero lines of code by Trevor Squires. Using modules, super and extend, Trevor came up with this alternative solution, which maintains identity as well. Bow down to his Ruby-fu.
Update 2: Never one to step back from a challenge, I see Trevor’s Ruby-fu and raise him with a bit of my own. I still feel my decorator implementation carries some weight, with a nice bit of syntatic sugar:
class Milk include Decorator end
class Whip include Decorator end
class Sprinkles include Decorator end
# normal coffee Coffee.new
# coffee with milk, whip and sprinkles Coffee.with :milk, :whip, :sprinkles
self.with method could do with being extracted into a Decoratable module but here’s a concrete implementation. Trevor’s method still has an advantage in that it maintains object identity, but I have some ideas to solve that. But alas, it is past 1am and bed beckons.
Update 3: Trevor fights back with his own self.with() implementation. Personally I prefer the
extend implementation, it eliminates the symbol to class trickery, and still maintains identity. His ’fu is still strong.
However, there is one downside to Trevor’s method: you can only apply a decorator to an object using extend once. But what if you wanted double sprinkles?
# or with a bit of sugar (no pun intended) Coffee.with :sprinkles, :sprinkles </code></pre>