terça-feira, 11 de dezembro de 2012

Módulos e Mixins com Ruby

No post sobre blocos em Ruby, rasguei elogios para a linguagem quanto à versatilidade e simplicidade. Neste post vou pular essa parte (que acredito estar implícita) e falar sobre módulos e mixins em Ruby.

Assim como os blocos, os módulos também estão presentes em toda parte no código de uma aplicação desenvolvida com Ruby on Rails. O framework Rails faz amplo uso dessa facilidade da linguagem Ruby para disponibilizar funcionalidades por todas as camadas da aplicação, o que também pode ser explorado pelo desenvolvedor em funcionalidades próprias.

Para quem vem da linguagem Java, módulos podem se parecer com as interfaces, no entanto, diferem em aspectos fundamentais.

Módulos x Interfaces x Herança Múltipla


A primeira grande diferença é que os módulos em Ruby implementam efetivamente métodos que serão "embutidos" em qualquer classe que os inclua. Outra grande diferença é que os módulos, diferentemente das interfaces, não têm como finalidade desempenhar papel polimórfico no conceito de is-a envolvendo a tipagem dos objetos e a delegação da implementação especializada para as classes.

Este link, da DevMedia, esclarece bastante o conceito de polimorfismo para quem não está tão familiarizado.

Módulos, então, adicionam livremente funcionalidades prontas às classes, sem que a linguagem necessite aceitar herança múltipla, o que também ofereceria esta possibilidade mas distorceria os aspectos de tipagem e polimorfismo.

A nível de curiosidade, Java também não permite herança múltipla, mas também não oferece nativamente recurso similar aos módulos de Ruby, no entanto há um projeto nessa linha chamado Qi4j que merece uma conferida.


Module x Class


Focando agora no universo do Ruby, na API existe Module e Class. A Class herda de Module, que herda de Object, que herda de BasicObject, sendo o quarteto que faz a base da API do Ruby.



A principal diferença entre módulos e classes é que a Class implementa a possibilidade de instanciar (método new), ou seja, uma classe pode ser instanciada, enquanto um módulo não oferece esta possibilidade. Outra diferença, relacionada a esta primeira, é que uma classe pode especificar herança e ser herdada, enquanto módulos não o podem.

As semelhanças são que módulos podem conter: 
  • métodos de instância (para as instâncias das classes que os incluírem)
  • métodos de módulo (como métodos de classe, também conhecidos como métodos estáticos)
  • definição de classes e constantes


Tanta teoria e nenhum módulo à vista ainda.
Módulos então têm duas funções em Ruby: namespaces e mixins.


Módulos como namespace



Desempenhando o papel de namespace, módulos incluem somente elementos acessíveis de forma estática, podendo até possuir métodos de instância, porém sem que estes últimos façam sentido em função de seu papel de namespace. Um exemplo na API é o módulo Math que possui por exemplo o método sqrt que calcula a raiz quadrada de um determinado número.

Math.sqrt(1024) retorna o inteiro 32.

Uma implementação própria, inútil na prática mas útil como exemplo, seria:
module FuncoesMatematicas
  def FuncoesMatematicas.calcular_raiz_quadrada(numero)
    return Math.sqrt(numero)
  end
end

FuncoesMatematicas.calcular_raiz_quadrada(1024) retorna o inteiro 32.

E um exemplo de namespace que contém classes e constantes acessíveis externamente seria:
module Utilidades
  VALOR_PADRAO = "valor padrao"

  class SimuladorDeUniverso
    ...
    def simular_planetas
      ...
    end
    ...
  end
end

Para classes e constantes definidas dentro do escopo de um método o acesso é feito com :: diferentemente dos métodos estáticos exemplificados antes.
Utilidades::SimuladorDeUniverso
Utilidades::VALOR_PADRAO

Se você achou familiar, está plenamente correto. Em uma aplicação Rails, por exemplo, as classes de modelo por padrão herdam de ActiveRecord::Base, ou seja, herdam da classe Base que está contida em um módulo chamado ActiveRecord.
class Pessoa < ActiveRecord::Base
  ...
end


Módulos como mixins


E aqui entra o recurso mais interessante oferecido pelos módulos. Abaixo um exemplo bem simples:

module InstrumentoAcustico
  def afinar
    puts "afinando"
  end
end 

module InstrumentoEletronico
  def ligar
    puts "ligando"
  end

  def desligar
    puts "desligando"
  end
end  

module InstrumentoDeCordas
  def trocar_cordas
    puts "trocando as cordas"
  end
end  

class Violao
  include InstrumentoAcustico
  include InstrumentoDeCordas
end 

class PianoEletrico
  include InstrumentoEletronico
end

meu_violao = Violao.new
meu_violao.afinar
meu_violao.trocar_cordas

meu_piano = PianoEletrico.new
meu_piano.ligar
meu_piano.desligar


Com os módulos e classes definidos, os comandos ao final do código imprimem:

afinando
trocando as cordas
ligando
desligando


Difícil postar uma explicação resumida sobre um recurso que abre tantas possibilidades na linguagem Ruby, por isso para mais exemplos e também detalhes sobre a implementação de módulos, namespaces, mixins, cuidados necessário, inclusão dinâmica de módulos, entre outras coisas, recomendo o capítulo 12 do livro The Book of Ruby e o capítulo 16 do livro Eloquent Ruby.

Até a próxima!

Um comentário:

  1. Really Good blog post.provided a helpful information.I hope that you will post more updates like this Ruby on Rails Online Training Bangalore

    ResponderExcluir