Design Pattern in Ruby 1: Decorator pattern

Decorator pattern, something that gives you two type of things
  • 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 Beverage class. In the same way the concrete condiment classes inherit the abstract CondimentDecorator class.
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.
The abstract Beverage class:
module AbstractBeverage
    def cost
        raise Exception, %Q|You should implement the cost for #{self.class}|
    end
end
The 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.cost
You 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.

Comments

  1. Not bad, but to me the end syntax looks a bit confusing.

    In 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.

    ReplyDelete
  2. 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?
    Anyway, Here is another interesting discussion on this topic (http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/230487b8bb86059a).

    Thank you

    ReplyDelete
  3. hey samiron, nice post...

    ReplyDelete

Post a Comment

Popular posts from this blog

Ajax form submission with tiny MCE editor

Samsung BADA, Starting with sample application

Bada tutorial: Application basics