Tuesday, July 25, 2006

Ruby, the Consummate Delegator - Part 2

Back in June, I posted about using Ruby's method_missing for simplified delegation.

Well, I might have known Ruby would have even more options for handling delegation.

The following achieves the same result without having to implement method_missing. DelegateClass implements method_missing for you, along with some other conveniences.
require 'delegate'

class CardDeck < DelegateClass(ShuffleArray)
def initialize(cards)
super(ShuffleArray.new(cards))
end
end
I suppose the downside here is that we are back to using inheritance, even though we're delegating to ShuffleArray, and we wanted to avoid that since we thought CardDeck was going to evolve a bit more.

Now if we wanted to selectively delegate certain methods to certain objects, we could instead extend our class with Forwardable. Here we avoid inheritance at the expense of some repetition as we'd have to call def_delegators for each method that we want to expose from ShuffleArray.
require 'forwardable'

class CardDeck
extend Forwardable

def initialize(cards)
@cards = ShuffleArray.new(cards)
end

def_delegators :@cards, :shuffle, :size,
:[], :find,
:reverse, :each,
:sort_by
end
This might be a better approach as we've left CardDeck open for later inheritance and we can now delegate to other composed objects in the future. Though we could still do that with method_missing with just an extra if block, so I'm not sure how much we've gained here.

For one thing we're somewhat back to writing those pesky delegation methods that we hoped to avoid in the first place. def_delegators certainly makes it a bit easier than doing the same thing in say C# or Java, and I suppose we shouldn't be too lazy. But we also could forget to pass in some methods and if Array or ShuffleArray gets redefined at runtime, then we won't delegate to them like we would with method_missing.

I'll grant you this whole CardDeck example is a bit contrived and if I thought about it a bit more I could probably come up some better examples of when to use these different approaches "in the real world."

For example, Forwardable could be useful for writing an adapter for a class that needs to conform to the same interface as another, or for building a façade to expose a subset of a delegate's interface.

I'd certainly be interested in hearing about how others have used these nifty features in Ruby.

0 Comments:

Post a Comment

<< Home