Delegation in Ruby
This is my first article in http://railsmagazine.com, it was published in issue 1, so basically I'm just republishing it here again.
"Separate changeable parts from others that remain the same" and "composition is preferred to inheritance" are 2 common design principles when you start designing in OOP world. However and while the first seems to be logical, a one might wonder why it's preferable to use composition over inheritance, and that's a logical question, lets answer it via an example:
Let's suppose that we have a Robot that has a heat sensor, a one would write a very simple UML:
This design has several drawbacks:
1.There is a strong probability to have another type of robots that don't have heat sensors(breaks the first design principle: separate changeable code from static one).
2.Whenever I want to modify anything related to the heat sensor, I have to change the robot class(breaks the first design principle).
3.Exposure of heat sensor methods to in Robot class.
Let's enhance this class a bit:
Well, now this is an inheritance based design and it solves the first problem, but it's still incapable to solve the other 2 problems related to the heat sensor. Let's do another enhancement:
Now this is a typical design, based on composition rather than inheritance, where we could solve the above 3 problems, and moreover we gained a new thing: we can now abstract the HeatSensor for future uses.
Now what's delegation?
Delegation is the process of delegating functionality to the contained parts.
If you look carefully at the previous figure, you will notice that the VolcanoRobot is still having the 3 methods that are related to the sensor, well those are a wrapper methods, they do nothing but to call the sensor corresponding ones, and that's exactly what delegation is, just delegate functionality to the contained parts(delegates).
Delegation comes along with composition to provide a flexible neat solutions like the one we had above, and also it serves the principle "separate changeable code from static one" ,but that also comes with a tax: a need of wrapper methods, and extra time needed in processing because of the call of these wrapper methods.
Ruby and delegation
Now let's have a code example:
We have a multi purpose Robot that has an arm and a heat sensor, the robot does several jobs, like packaging boxes, stacking them and measuring the heat.
we will use composition and delegation as follows:
As you can see, i have 3 wrapper methods(stack,package and measure_heat) in Robot class that are doing nothing but to call the contained objects corresponding methods.
This is really a nasty thing, specially when there are lots of contained objects.
However there are 2 libs that comes to the rescue to in ruby, Forwardable and Delegate. Let's check them one by one.
Forwardable lib
Forwardable lib is library that supports delegation, it has 2 modules Forwardable and SingleForwardable:
Forwardable module
The Forwardable module provides delegation of specified methods to a designated object, using the methods def_delegator and def_delegators.
def_delegator(obj, method, alias = method) : Defines a method method which delegates to obj. If alias is provided, it is used as the name for the delegate method.
def_delegators(obj, *methods): Shortcut for defining multiple delegator methods, but with no provision for using a different name.
Let's refactor our robot example to make it Forwardable module:
Well, that's a neater solution as you can see.
SingleForwardable module
The SingleForwardable module provides delegation of specified methods to a designated object, using the methods def_delegators. This module is similar to Forwardable, but it works on objects themselves, instead of their defining classes.
Delegate Lib
Delegate lib is another lib that provides delegation, i'll explain 2 ways to use it:
DelegateClass method
Use the top level DelegateClass method which allows you to easily setup delegation through class inheritance. In the following example, I want to make a new class called CurrentDate, which holds the current date and some extra methods, at the same time I'm delegating to normal date objects:
SimpleDelegator class
Use it to delegate to an object that might be changed:
As you can see, we made 2 objects and then delegated to them consequently.
What about Rails?
Rails adds new functionality called "delegate":
Which provides a delegate class method to easily expose contained objects’ methods as your own. Pass one or more methods (specified as symbols or strings) and the name of the target object as the final :to option (also a symbol or string). At least one method and the :to option are required.
go to your console and create a dummy project ,then cd to that project, and fire the rails console:
I strongly urge you to check the whole provided examples in rails API documetation,to check also how to use this effectively with ActiveRecord.
Before I finish this article I want to share you the code of delegate method form rails API documentation, I'll add some comments on the code to explain you what is going on:
That's it for this article, we have covered 5 points:
1-Composition vs inheritance.
2-What delegation is, and why it's used.
3-Ruby Forwardable lib.
4-Ruby Delegate lib.
5-Rails delegate method.