Metaprogramação em Ruby

Continuando a série de posts sobre a linguagem Ruby, vamos ver como funciona a metaprogramação de uma maneira bem prática. Se você perdeu os outros posts dê uma conferida aqui: Classes, escopos e convenções em Ruby e Reflexão em Ruby.

Adicionando código em tempo de execução

Vamos criar duas classes, Pai e Filho. A classe filho não possui nenhum método, já a classe Pai possui um método que vai ensinar o filho a andar de bicicleta:

1.9.3p125 :001 > class Filho
1.9.3p125 :002?> end
 => nil 
1.9.3p125 :003 > class Pai
1.9.3p125 :004?>   def ensina(filho)
1.9.3p125 :005?>     def filho.andar_de_bicicleta
1.9.3p125 :006?>       puts "sei andar de bicicleta"
1.9.3p125 :007?>     end
1.9.3p125 :008?>   end
1.9.3p125 :009?> end
 => nil

Agora vamos instanciar um filho e verificar se ele sabe andar de bicicleta:

1.9.3p125 :010 > filho = Filho.new
 => #<Filho:0x9e37610> 
1.9.3p125 :011 > filho.respond_to? :andar_de_bicicleta
 => false

Agora vamos fazer com que o pai ensine ao filho andar de bicicleta:

1.9.3p125 :012 > pai = Pai.new
 => #<Pai:0x9e42858> 
1.9.3p125 :013 > pai.ensina filho
 => nil 
1.9.3p125 :014 > filho.respond_to? :andar_de_bicicleta
 => true 
1.9.3p125 :015 > filho.andar_de_bicicleta
sei andar de bicicleta
 => nil

Maravilha! Adicionamos um método em um objeto em tempo de execução!

Alterando código em tempo de execução

Mas, e se o objeto já possuir um método? Por exemplo, vamos supor que o filho já possua um método “andar_de_bicicleta”?

1.9.3p125 :001 > class Filho
1.9.3p125 :002?>   def andar_de_bicicleta
1.9.3p125 :003?>     puts "nao sei andar de bicicleta"
1.9.3p125 :004?>   end
1.9.3p125 :005?> end
 => nil 
1.9.3p125 :006 > class Pai
1.9.3p125 :007?>   def ensina(filho)
1.9.3p125 :008?>     def filho.andar_de_bicicleta
1.9.3p125 :009?>       puts "sei andar de bicicleta"
1.9.3p125 :010?>     end
1.9.3p125 :011?>   end
1.9.3p125 :012?> end
 => nil 
1.9.3p125 :013 > filho = Filho.new
 => #<Filho:0x9f36ca0> 
1.9.3p125 :014 > pai = Pai.new
 => #<Pai:0x9ef5bb0> 
1.9.3p125 :015 > filho.andar_de_bicicleta
nao sei andar de bicicleta
 => nil

Então se agora o pai ensinar ao filho, o que acontece?

1.9.3p125 :016 > pai.ensina filho
 => nil 
1.9.3p125 :017 > filho.andar_de_bicicleta
sei andar de bicicleta
 => nil

Como era de se esperar, o método foi alterado. Como definido lá no começo, metaprogramação é a capacidade de adicionar ou modificar o código em tempo de execução. Pelo exemplo ficou bem claro não? Vale notar também que apenas aquela instância da classe “Filho” foi alterada. Se forem instanciados outros filhos, nenhum deles vai ter as alterações feitas pelo pai, a menos que o pai ensino o filho.

Acessores ruby

Certo, então vamos ver um exemplo bem mais útil, os acessores ruby. Vimos antes os escopos das variáveis em Ruby, um destes escopos é o das variáveis de instância, que começam com ‘@’. Como é possível então ler/alterar os valores destas variáveis? No Java existem os famosos getters e setters, que são métodos para ler/alterar valores das variáveis. Em ruby é possível fazer da mesma maneira, veja o código a seguir:

1.9.3p125 :001 > class Produto
1.9.3p125 :002?>     def initialize(nome, preco)
1.9.3p125 :003?>         @nome = nome
1.9.3p125 :004?>         @preco = preco
1.9.3p125 :005?>       end
1.9.3p125 :006?>     
1.9.3p125 :007 >       def get_nome
1.9.3p125 :008?>         @nome
1.9.3p125 :009?>       end
1.9.3p125 :010?>     
1.9.3p125 :011 >       def set_nome(nome)
1.9.3p125 :012?>         @nome = nome
1.9.3p125 :013?>       end
1.9.3p125 :014?>   end
 => nil 
1.9.3p125 :015 > 
1.9.3p125 :016 >   refri = Produto.new("refri", 2.50)
 => #<Produto:0x8bfd670 @nome="refri", @preco=2.5> 
1.9.3p125 :017 > refri.get_nome
 => "refri" 
1.9.3p125 :018 > refri.set_nome "refrigerante"
 => "refrigerante" 
1.9.3p125 :019 > refri.get_nome
 => "refrigerante"

Mas a linguagem ruby também possui uma característica bem forte, que é buscar o máximo de legibilidade. Então, que tal se no lugar de get_nome e set_nome nós fizermos o seguinte:

#...
1.9.3p125 :007 >       def nome
1.9.3p125 :008?>         @nome
1.9.3p125 :009?>       end
1.9.3p125 :010?>     
1.9.3p125 :011 >       def nome=(nome)
1.9.3p125 :012?>         @nome = nome
1.9.3p125 :013?>       end
#...

Agora poderíamos utilizar o código de uma maneira bem mais legível:

#...
1.9.3p125 :016 >   refri = Produto.new("refri", 2.50)
 => #<Produto:0x8bfd670 @nome="refri", @preco=2.5> 
1.9.3p125 :017 > refri.nome
 => "refri" 
1.9.3p125 :018 > refri.nome = "refrigerante"
 => "refrigerante" 
1.9.3p125 :019 > refri.nome
 => "refrigerante"

Legal não? Tá, mas onde entra a metaprogramação na história? Bom, isto que acabamos de fazer foi criar acessores para o atributo “nome” da classe “Produto”. Em ruby podemos fazer isto de maneira bem mais simples, utilizando os “attr_”. Vejam o código primeiro:

1.9.3p125 :001 > class Produto
1.9.3p125 :002?>   
1.9.3p125 :003 >       attr_accessor :nome
1.9.3p125 :004?>     
1.9.3p125 :005 >       def initialize(nome, preco)
1.9.3p125 :006?>         @nome = nome
1.9.3p125 :007?>         @preco = preco
1.9.3p125 :008?>       end
1.9.3p125 :009?>   end
 => nil 
1.9.3p125 :010 > 
1.9.3p125 :011 >   refri = Produto.new("refri", 2.50)
 => #<Produto:0x94474ac @nome="refri", @preco=2.5> 
1.9.3p125 :012 > refri.nome
 => "refri" 
1.9.3p125 :013 > refri.nome = "refrigerante"
 => "refrigerante" 
1.9.3p125 :014 > refri.nome
 => "refrigerante"

Então o que é aquele “attr_accessor”? Nada mais é do que um método, que recebe como parâmetros um conjunto de símbolos, que representam variáveis de instância, e vão adicionar na sua classe os getters e setters daqueles atributos. Veja que passamos como parâmetro “:nome”, então o método vai criar “def nome” e “def nome=(nome)” para que possamos acessar este atributo de fora da classe. Sensacional não? Conseguimos diminuir consideravelmente a quantidade de código para escrever e ler.

Além do “attr_accessor” existem o “attr_reader”, que cria apenas o método para ler o atributo e o “attr_writer” que cria apenas o método para alterar o atributo. Este tipo de abordagem é muito utilizada (sério) no ruby, se quer um exemplo procure como o Rails faz para criar relacionamentos entre entidades. E não só o Rails, várias outras gems utilizam esta prática para reduzir o código.

Anúncios

Um comentário sobre “Metaprogramação em Ruby

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s