Design Pattern in Ruby 1: Decorator pattern
Decorator pattern, something that gives you two type of things
Notice that, here all concrete beverages are inherited from the abstract class
- A plain vanilla cake and
- Cream, cheery etc to decorate the cake :D
How does it sounds? Mouth watering design pattern right? Anyway, dont get too excited!! we're gonna dive into the code shortly 8(
You might have already known all the nuts and bolts about this pattern, but I was really wondering about the implementation of this pattern in Ruby.
Here I will work with the example given in Head first design patterns (Chapter 3). There they gave the example of a coffee house. Like our plain vanilla cake, the coffee house also have two different type of things:
- Beverages: HouseBlend, DarkRoast, Espresso and Decaf
- Condiments: Milk, Mocha, Soy and Whip
So that you can have a DarkRoast with some Mocha and Soy :)
However, I think we should have a look at the class diagram first
Notice that, here all concrete beverages are inherited from the abstract class
Beverage
. And all concrete condiment classes have inherited the CondimentDecorator
. Also the CondimentDecorator
inherits the abstract Beverage
class. Well this is because all the beverages and the condiments should be of same type for those languages where variable type matters. creating confusion?? no problem... lets go ahead.
Here we are talking about the decorator pattern in Ruby. In ruby there are no constraints of data type because of its dynamic behavior. So I don't think we need our
CondimentDecorator
class to be inherited from the abstract Beverage
class. So Could we modify the above class diagram a little bit, like following--
So, here we have two different sets of classes. All concrete beverages inherit the abstract
Now lets have the implementation. As ruby doesnt provide any abstract class concept we will implement this using module. Here we have our modules to ensure that we have implemented all our necessary methods in the classes.
Beverage
class. In the same way the concrete condiment classes inherit the abstract CondimentDecorator
class.
The abstract
Beverage
class:module AbstractBeverage def cost raise Exception, %Q|You should implement the cost for #{self.class}| end endThe concrete implementations of the beverages ( Only DarkRoast and Espresso are shown):
class DarkRoast include AbstractBeverage COST = 0.99 def cost return COST end end class Espresso include AbstractBeverage COST = 1.99 def cost return COST end end
the abstract CondimentDecorator:
module AbstractCondiment def cost raise Exception, %Q|You should implement the cost for #{self.class}| end end
The concrete implementations of the condiments ( Only Mocha and Whip are shown ):
class Mocha COST = 0.20 include AbstractCondiment def initialize(beverage) @beverage = beverage end def cost return COST + @beverage.cost end end class Whip COST = 0.10 include AbstractCondiment def initialize(beverage) @beverage = beverage end def cost return COST + @beverage.cost end end
Now lets have a DarkRoast with double Mocha and a Whip :D
# The darkroast dark_roast = DarkRoast.new #Double Mocha dark_roast = Mocha.new(dark_roast) dark_roast = Mocha.new(dark_roast) # and a Whip dark_roast = Whip.new(dark_roast) #Ooops! how much it cost ??? puts dark_roast.costYou can download the full source code from here. Please share your ideas behind the ruby implementation of this pattern. I will continue writing on other patterns one by one.
Till then keep yourself tuned :) Thank you.
Not bad, but to me the end syntax looks a bit confusing.
ReplyDeleteIn Ruby I would leverage the power to redefine the + operator (in the beverage class), then you could do:
dark_roast = DarkRoast.new
dark_roast += Mocha.new
dark_roast += Whip.new
Now much more obvious to the casual observer what is going on.
The new + method would obviously check if the argument had a cost method and then add that to it's own, plus any other information that had to be ported.
Hello Ginty, Thanks for your comments. Yes you definitely made the code more readable. But dont you think that violates the sole target of decorator pattern. In your way are we really adding new behavior to dark_roast object?
ReplyDeleteAnyway, Here is another interesting discussion on this topic (http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/230487b8bb86059a).
Thank you
hey samiron, nice post...
ReplyDelete