How to Add Functionality to Ruby Classes with Decorators

Decorators allow us to add behavior to objects in runtime and don’t affect other objects of the class. Decorators can be applied when you need to dynamically add and remove responsibility to a class. The decorator pattern is a helpful alternative to creating sub-classes. They give additional functionality to a class while still keeping the public API consistent. Let’s look at an example to understand the importance of Ruby Decorators.

consider we have a Tattoo class with a price method that returns 300.

Class Tattoo
def price
300
end
end

Now we will add an extra color as a feature, and the price would be increased by 150

The simplest way is to create a TattooWithColour subclass that returns 450 in the price method.

class TattooWithColour < Tattoo
def price
450
end
end

Next, we need to represent a big tattoo that adds 200 to the price of our tattoos. We can represent this using a BigTattoo subclass of Tattoo.

class BigTattoo < Tattoo
def price
500
end
end

We could also have bigger sized tattoos and they may add further price to our BigTattoo. If we were to consider that these tattoos types could be used with colours, we would need to add BigTattooWithColour and BiggerTattooWithColour subclasses.

With this method, we end up with total of 6 classes. Even Double that the number if you want to represent these combinations with extra designs on tattoo.

Inheriting dynamically with modules

To simplify our code, we may use modules to dynamically add behavior to our Tattoo class. Let’s write ColourTattoo and BigTattoo modules for this.

module ColourTattoo
def price
super + 150
end
end

module BigTattoo
def price
super + 200
end
end

Now we can extend our tattoo objects dynamically using the Object#extend method.

tattoo = Tattoo.new
tattoo.extend(ColourTattoo)
tattoo.extend(BigTattoo)

This is good improvement over our inheritance based implementation. Instead of having sub classes, we just have one class and 3 modules. If we needed to add extra design to the equation, we need just four modules instead of 12 classes.

Applying the decorator pattern

This module based solution has simplified our code greatly, but we can still improve it by using the decorator. We will consider a BiggerTatto as being formed by twice adding 150 to the cost of a Tattoo.

We can’t do this by our module based approach. It would be tempting to call tattoo.extend(BigTattoo) twice to get BiggerTattoo. Extending module second time has no effect when we have already used extend ones.

If we were to continue using the same implementation, we would need to have a BiggerTattoo module that returns super + 300 as the cost. Instead, we can use decorator that can be composed to build complex objects. We start with a decorator called BigTattoo that is a wrapper around a Tattoo object.

class BigTatto
def initialize(tattoo)
@tattoo = tattoo
end

def price
@tattoo.price + 150
end
end

Bigger Tattoo can now be created by using this wrapper twice on a Tattoo object.

tattoo = Tattoo.new
big_tattoo= BigTattoo.new(tattoo)
bigger_tattoo = BigTattoo.new(big_tattoo)

We can similarly represent colour tattoo using a TattooWithColour decorator. Using just three classes, we are now able to represent 6 types of tattoo.

With rich expertise in all facets of Ruby On Rails Development, we at Railscarma, offer you a wide range of services to help you implement a comprehensive personalized strategy to communicate with your prospects and your customers at the right time, through right channels. For more details Contact us.

Leave a Comment

Your email address will not be published. Required fields are marked *