Consulting. Training. Development.

Delegation on a method by method basis with Forwardable

When we need to reuse code we could use inheriatance – create a new child class and add/override some methods. But it could lead to complex inheritance chains and extra methods which is not needed in a new class. Do we have a better alternative? Sure! It’s object composition.

Favor ‘object composition’ over ‘class inheritance’. (Gang of Four 1995:20)

Object composition allows us to create more reusable pieces of code that could be combined together keeping our code simple and understandable.

Let’s take an example of making a new interface over existing object. For example, we need a SimpleQueue class which is based on Array but have more idiomatic interface – enqueuing using enq method and dequeuing using deq method.

To achieve it we could just define those methods and call push and shift methods inside them:

1
2
3
4
5
6
7
8
9
10
11
12
13
class SimpleQueue
  def initialize
    @queue = []
  end

  def enq(value)
    @queue.push(value)
  end

  def deq
    @queue.shift
  end
end

Method delegation is commonly used so Ruby Stdlib have some sugar which helps us doing delegation on a method by method basis. Using Forwardable we’re able to delegate methods in declarative way:

1
2
3
4
5
6
7
8
9
10
11
12
require 'forwardable'

class SimpleQueue
  extend Forwardable

  def initialize
    @queue = []
  end

  def_delegator :@queue, :push, :enq
  def_delegator :@queue, :shift, :deq
end

So def_delegator defines enq method as a call to push method on @queue object. The same with deq method.

There are also some array methods that could be useful for our queue object. We can easily delegate them all at once using def_delegators:

1
  def_delegators :@queue, :clear, :first, :size

Here is an example of using a new SimpleQueue class which has a new interface based on the existing one:

1
2
3
4
5
6
queue = SimpleQueue.new
queue.enq 1, 2, 3, 4
queue.deq   # => 1
queue.first # => 2
queue.size  # => 3
queue.clear

And it’s important that SimpleQueue class has only methods that it has to have and doesn’t have any additional methods inherited from Array.

Forwardable library also includes a SingleForwardable module which might be useful in API client libraries. Consider we created an API client library with the following interface:

1
2
client = ApiLibrary::Client.new
client.some_api_call

It would be great to have an option to call API methods on a global client object instead of making a new client object every time we needed to do a request. We could use SingleForwardable to delegate methods to a global client object:

1
2
3
4
5
6
7
8
9
module ApiLibrary
  extend SingleForwardable

  def_delegators :client, :some_api_call, :another_api_call

  def self.client
    @client ||= Client.new
  end
end

SingleForwardable defines methods on object which it’s called on. So def_delegators declaration creates class methods in the example above and makes possible to perform API calls without instantiating an API client object manually:

1
2
ApiLibrary.some_api_call
ApiLibrary.another_api_call

If you were using both Forwardable and SingleForwardable in one class then you would need to call def_instance_delegator and def_single_delegator methods.

May objects be with you.

Comments