Delegação em Ruby utilzando o módulo Forwardable

Criado em: 21/02/2013

Em Ruby temos o poder de fazer overloading de operadores, ou seja definirmos nossa própria implementação para os operadores.

Quando temos uma classe que é uma coleção é uma boa idéia, se fizer sentido, definirmos os operadores <<, [] e []= pois assim teremos uma interface igual a de um Enumerable. Tecnicamente os operadores [] e []= não são operadores e sim métodos, em que o Ruby aplica um açucar sintático assim quando fazemos foo[2] estamos chamando o método [] em foo, passando o parâmetro 2 e quando fazemos foo[2] = 4 estamos na realidade chamando o método []= em foo passando os parâmetros 2 e 4, por isso conseguimos fazer overloading destes métodos também.

Vamos a um exemplo:

class Basket

  def initialize
    @items = []
  end

  def total_of_items
    @items.size
  end

  def <<(item)
    @items << item
  end

  def [](index)
    @items[index]
  end

  def []=(index, item)
    @items[index] = item
  end
end

criamos uma classe Basket que é nossa cesta, seu papel é ser uma coleção de items, vamos vê-la em ação:

basket = Basket.new
basket << "Morango"
basket << "Manga"
basket << "Banana"
basket[3] = "Pera"
p basket[0] # => "Morango"
p basket[3] # => "Pera"
p basket.total_of_items # => 4

Como pode ver agora nossa classe se comporta como um Enumerable no entanto se observar bem os métodos que implementamos, nada mais são do que delegar para os métodos do Enumerable, vamos a uma implementação mais inteligente.

Utilizando o Forwardable

O Ruby possui o módulo Forwardable que nos permite delegar métodos específicos para um objeto. Ou seja poderemos delegar os métodos de array, para o nosso próprio array @items. Vamos ao código:

require 'forwardable'

class Basket
  extend Forwardable

  def initialize
    @items = []
  end

  def_delegator :@items, :size, :total_of_items
  def_delegators :@items, :<<, :[], :[]=
end

Ao utilizarmos a nossa nova classe Basket obtemos o mesmo resultado da classe anterior:

basket = Basket.new
basket << "Morango"
basket << "Manga"
basket << "Banana"
basket[3] = "Pera"
p basket[0] # => "Morango"
p basket[3] # => "Pera"
p basket.total_of_items # => 4

Vamos agora entender o que aconteceu.

def_delegator

Primeiro utilizamos o def_delegator que nos permite definir um delegator, para um único método do objeto e opcionalmente definir um nome diferente ao método. No nosso exemplo usamos o Basket#total_of_items pois para a nossa interface faz mais sentido este nome do que apenas Basket#size.

def_delegators

Como você já deve imaginar o def_delegators nos permite definir diversos delegators, para diversos métodos do objeto, no entanto não é possível definir um nome diferente ao método. Utilizamos ele para criar os demais métodos <<, [] e []=.

Como pode ver o uso do Forwardable fez nossa classe mais enxuta e não precisamos redefinir os métodos já existentes em nosso objeto. Lembrando que pode-se definir qualquer método de um dado objeto, cabe a você fazer as escolhas certas para manter uma interface clara.

RSpec: Crie especificações executáveis em Ruby
Aprimore as suas habilidades enquanto escreve testes com o meu livro RSpec: Crie especificações executáveis em Ruby

Comentários

Comentários powered by Disqus