Ruby: Módulos, Mixins e Namespaces

Origens e Influências

editar
 
Logo

A linguagem Ruby surgiu a partir de discussões de Yukihiro Matsumoto (Japão, 1965), também conhecido como Matz, que desejava uma linguagem de scripts verdadeiramente orientada a objetos que fosse sintaticamente simples, portável e que tivesse outras características que pudessem aumentar a produtividade de quem a usasse.

Ele alegava conhecer Perl, mas não gostava dela porque ele achava que não parecia ser uma linguagem séria e também conhecia Python, mas ela não era exatamente orientada a objetos.

Ele queria uma linguagem com a qual fosse divertido programar e não um pesadelo. Essa linguagem deveria ser primordialmente compreensível por humanos e em segundo lugar por máquinas.

Como Matz não encontrou a linguagem ideal, ele resolveu criar uma. Ele passou vários meses escrevendo um interpretador e em 1995 divulgou seu trabalho em newsgroups no Japão. Para definir a linguagem ele buscou inspiração nas melhores estruturas e conceitos de Smalltalk, Perl, Ada, Eiffel e LISP.

Outras versões saíram em 1996 e 1997, mas só em 1998 foi lançada uma versão verdadeiramente estável, a 1.2. Nesse ano também ele começa a divulgar sua linguagem fora do Japão ao criar uma lista de discussão sobre Ruby em inglês.

Em 1999 Matz e Keiju Ishitsuka publicam o primeiro livro em japonês sobre Ruby, cujo título traduzido para o inglês era: “The Object-Oriented Scripting Language Ruby” . Um livro em inglês foi publicado no ano seguinte: “Programming Ruby” . Nessa época a linguagem já era mais popular do que Python no Japão e começava a se espalhar pelo mundo.

 
Yukihiro Matsumoto em março de 2007.

Em 2005, com o framework Ruby on Rails, um ambiente de desenvolvimento rápido de aplicações que permitia uma grande produtividade por parte dos programadores, Ruby fica realmente popular. Esse ambiente é muito usado na comunidade de criadores de aplicações Web. Ele foi criado por David H. Hansson (Dinamarca, 1979) em Ruby, e graças a ele, Ruby tornou-se uma das linguagens mais usadas hoje no mundo, estando em 11º lugar na tabela Tiobe em fevereiro de 2016.

Ruby foi projetada para seguir o princípio conhecido como POLA, “Principle Of Least Astonishment”, o que significa que as estruturas da linguagem não devem surpreender os programadores. Em muitas linguagens de programação encontramos essas surpresas, como estruturas que funcionam de forma inesperada.

Eventualmente Matz foi criticado por pessoas que diziam ter programado sempre com outras linguagens e que ficavam surpresas com alguns comportamentos de Ruby, mas ele esclarecia que o princípio só se aplica a quem já é programador Ruby; quem vem de outras culturas de programação certamente vai achar algumas coisas estranhas. Ele observa que outras linguagens, como C++, por exemplo, continuavam surpreendendo mesmo quem já as programava a muitos anos e era isso o que ele queria evitar que Ruby fizesse.

Ruby está posicionada entre no top 10 da maioria dos índices que medem o crescimento da popularidade de linguagens de programação pelo mundo todo (tais como o índice TIOBE). O Ruby também é totalmente livre. Não somente livre de custos, mas também livre para utilizar, copiar, modificar e distribuir.

Linha do Tempo

editar
 

Principais marcos da origem da linguagem Ruby e de seu framework principal Ruby on Rails.

Classificação da Linguagem

editar

Ruby é uma linguagem de programação de alto nível, interpretada, multiparadigma, com gerenciamento de memória automático e dinamicamente, implicitamente e fortemente tipada.

Interpretada

Os interpretadores se diferenciam por serem de uma ou de múltiplas passadas. Isso implica diferenças na ordem do código e no desempenho da linguagem, pois o interpretador de passada única lê serialmente o código. Sendo assim, todas referências utilizadas já devem ter sido lidas e criadas pelo interpretador. Ruby, na maioria de usas implementações, faz uso do interpretador de passada única.

Multiparadima

Dar liberdade ao desenvolvedor escolher o paradigma que queira, suporte a programação orientada a objetos, imperativa e funcional. A maior influência na orientação de objetos de Ruby é SmallTalk, a pioneira nesse paradigma, onde não existe tipos de dados que não são objetos. Por exemplo, não existem inteiros, booleandos e caracteres, ao invés disso são objetos inteiros, objetos booleanos e objetos caracteres.

Tipagem Dinâmica

Quer dizer que, a cada interação, o tipo é verificado. Isso fica claro no seguinte exemplo:

x = 100 
(1..4).each{ x = x * 100;  puts "#{ x.class} #{x}"}

Que gera o resultado:

Fixnum 10000

Fixnum 1000000 Fixnum 100000000

Fixnum 10000000000

Implicitamente Tipada

Continuando no mesmo exemplo, quando fizemos x = 100 não precisamos declarar o tipo de x. Ou seja, não foi necessário fazer algo como: Fixnum x = 100. Isso acontece pelo fato do Ruby detectar o tipo de cada variável em tempo de execução.

Fortemente Tipada

Todas as variáveis devem ter um tipo. Ou seja: fazer parte de uma classe no caso do Ruby - e que cada tipo segue a risca seu contrato. Por exemplo:

a = 100
b = "Ruby on Rails"
a + b
 
TypeError: String can't be coerced into Fixnum
    from (irb):54:in `+'
    from (irb):54

Como é possível notar, em Ruby não podemos somar um String com Fixnum pois essa operação não está no contrato da classe String e Fixnum.

Módulos

editar

Descrição da Funcionalidade

editar

Um módulo nada mais é do que uma coleção de métodos e constantes. Ou seja, em um módulo podemos definir quantos métodos e constantes desejarmos, e depois podemos incluir esse módulo em outros módulos e até mesmo em várias classes, realizando, o compartilhamento de código.

É quase inevitável, que ao primeiro contato com módulos, não reparemos uma sua certa similaridade com classes, pois ambos os recursos nos permitem agrupar uma coleção de métodos, constantes, compartilhar código e outras definições de classes e módulos.

No entanto, existem diferenças consideráveis entre eles, como por exemplo, o fato de que não se pode criar objetos a partir de um módulo. Outro ponto importante é que módulos não utilizam herança, e sim a prática da injeção de módulos que nos permitir compartilhar mais de um módulo, realizando assim, algo semelhante à herança múltipla (não suportada pela linguagem).

Os módulos disponibilizam dois grandes privilégios: Mixins e NameSpace.

Exemplos

editar

Exemplo Didático

editar

A criação de um módulo é bastante similar a de uma classe, no entanto, em oposição de se utilizar a palavra reservada class, o programador deverá utilizar a palavra module. Também é importante destacar que a nomenclatura de módulos segue o mesmo padrão de classes, onde definimos que a letra inicial de cada palavra deverá ser escrita em maiúscula (camel case).

 

O módulo recém criado possui apenas um método chamado 'a'.

Exemplo Realístico

editar

Suponhamos que você tenha a seguinte situação:

 


Nesse caso precisamos ter o mesmo método nas duas classes e uma horrível duplicação de código, uma péssima prática. Podemos resolver isso de maneira simples da seguinte forma:

 

Com a criação do módulo PagamentoMensal o método pagamento_mensal não precisa ser repetido nas classes Emprestimo e Vinculo.

Exemplo em Java

editar

O código em Java fica parecido com o código em Ruby para a utilização de apenas um módulo, pois podemos utilizar classe abstrata, pois a mesma tem o mesmo conceito de módulo (uma coleção de métodos onde a classe ou módulo não podem ser instanciados).

Por Java ser uma linguagem mais verbosa, é necessário mais linhas de código.

 


Mixins

editar

Descrição da Funcionalidade

editar

Embora não possamos criar instâncias de módulos, nós podemos incluí-los na definição de uma classe! Quando fazemos isso, todos os métodos de instância de um módulo se tornam disponíveis como métodos dos objetos da classe extendida também. Isso são Mixins. Os módulos incluídos em classes se comportam como "Superclasses" e eliminam qualquer necessidade de herança múltipla.

Os Mixins possibilitam utilizar o compartilhamento código. Onde podemos distribui métodos comuns entre várias classes, objetos e módulos.

Exemplos

editar

Exemplo Didático

editar
 
  1. Interação com a Classe O maior poder dos Mixins está quando o código do Módulo interage com o código da classe, como no exemplo do módulo Ingles.
  2. Variáveis de Instância O módulo que você inclui em uma classe pode criar variáveis de instância aos objetos da classe, assim como os métodos de acesso a essas variáveis.
     
  3. Include O include para incluir módulos em uma classe não tem nada a ver com arquivos. Se o módulo incluído está em um arquivo diferente, esse arquivo deve ser incluído usando require para que ele possa ser carregado antes de ser incluído. O include não copia os métodos para dentro da classe. As classes que incluem um mesmo módulo passam a apontar para as definições desse módulo. Caso o módulo seja alterado, todos as classes terão seus comportamentos modificados.
  4. Extend Para ‘injetar’ um módulo em um objeto existente devemos utilizar o comando extend, que por sua vez irá estender as propriedades definidas no módulo para o objeto desejado. Assim, o módulo não é incluído para todos objetos de uma classe, somente para o objeto estendido. Se um objeto a é estendido com o módulo B, esse objeto passará a se comportar como B definir.
     
    No exemplo acima, o método quack do módulo ActLikeADuck só é chamado quando o comando extend é atribuido ao objeto costumed_person.
    Classes também são objetos. Portanto elas podem ser estendidas com Módulos para adicionar novos métodos de classe
     

Exemplo Realístico

editar

1. Criar módulo: Já vimos anteriormente neste documento como criar um módulo. Neste exemplo o arquivo será chamado de modules.rb.

 
Figura 1 - Criação do módulo ConverterDolar e ConverterEuro.

Os módulos recém criados possuem 02 métodos de conversão, são eles: real_to_dolar, real_to_euro, dolar_to_real e euro_to_real. No intuito de demonstrar a utilização de constantes foi feita a declaração de DOLAR e EURO, para armazenarem de maneira estática a cotação das respectivas moedas.

2. Aplicar o Mixin: O próximo passo é a criação de uma classe que irá utilizar os recursos definidos nos módulos ConverterDolar e ConverterEuro. O nome dessa classe será Cambio e a mesma terá dois métodos denominados de menu e selected_action, que por sua vez terão as responsabilidade de dispor as opções disponíveis de conversão de moeda, solicitar uma resposta (opção selecionada) ao usuário e de acordo com essa resposta, invocar um dos métodos de conversão. O arquivo será chamado de cambio.rb.

 

Analisando o código acima, é possível perceber que a primeira iniciativa (através do require_relative) foi carregar o arquivo module.rb para o próprio arquivo cambio.rb. Isso foi necessário já que na linha de Nº 04 é pedido para incluir o módulo Converter na classe Cambio, no entanto, o módulo era um desconhecido para o arquivo cambio(.rb). Portanto, a partir da linha de Nº 01 o arquivo cambio(.rb) passou a ‘conhecer’ o arquivo modules(.rb) e assim as suas propriedades puderam ser referenciadas.

Portanto, o módulo ConverterDolar e o módulo ConverterEuro foram injetados em nossa classe Cambio através do comando include. Com isso, a classe Cambio ganhou acesso aos métodos do módulo ConverterDolar e o módulo ConverterEuro. Dessa forma, qualquer instância de Cambio poderá usufruir desses recursos injetados.

Então logo após o comando include, foi definido método menu. Esse método irá exibir o menu de opções e receber a opção de conversão selecionada pelo usuário, retornando-a no final. Depois, foi a vez do método selected_action. Ele primeiramente irá invocar o método menu e de acordo com o retorno obtido, irá chamar um dos métodos de conversão implementados pelo módulo Converter.

3. Executar: É necessário criar um arquivo e dentro dele instanciar um objeto com base na classe Cambio. O arquivo será chamado de main(.rb).

 
Figura 3 - Criação da Main

Perceba que após a criação do objeto novo_cambio com base na classe Cambio, fizemos esse objeto chamar o método selected_action (que invoca o método menu internamente).

 
Figura 4 - Execução da Main

Na figura acima é possível observar o funcionamento do nosso programa, no qual foi utilizado os métodos do módulo Converter de dentro da classe Cambio através do seu método selected_action. Resumindo, o objeto instanciado novo_cambio acessou os recursos do nosso módulo de forma ‘indireta’, através do método selected_action que se encarregou de acionar os mecanismos do módulo.

Isso só foi possível porque foi incluido tal módulo em nossa classe, fazendo com que os métodos definidos no módulo pudessem ser compartilhados com a classe.

Agora o próximo passo será fazer um objeto instanciado da classe Cambio acessar diretamente os métodos definidos nos módulos ConverterEuro e ConverterDolar. Para fazer isso é muito simples, basta solicitar os recursos do módulo a partir do objeto criado (instanciado).

 
Figura 5 - Acessando os recurso do módulo Converter diretamente do objeto recém criado.

Exemplo em Java:

editar

Diferente de Ruby em java não possuímos o conceito de mixins, porém temos a composição que é algo similar. Onde criamos classes com métodos específicos assim com fizemos com os módulos e ao invés de injetar as classes em uma classe que irá utilizar esses métodos nós instanciamos os objetos dessas classes para podermos utlizarmos seus métodos , no exemplo abaixo podemos ver claramente isso

1.Criação das classes: Criação das classes com métodos específicos. Ao contrário dos módulos, as classes podem ser instanciadas e isso nos ajudará no conceito de composição

 
Criação das classes ,para uso da composição


2-Aplicando composição: próximo passo é a criação de uma classe que irá utilizar os métodos definidos nas classes ConverterDolar e ConverterEuro. O nome dessa classe será Cambio e a mesma terá dois métodos denominados de menu e selected_action, que por sua vez terão as responsabilidade de dispor as opções disponíveis de conversão de moeda, solicitar uma resposta (opção selecionada) ao usuário e de acordo com essa resposta, invocar um dos métodos de conversão.Igual no exemplo de ruby , mas em java faremos a composição, podemos ver no escopo da classe que, estamos criando um objeto para as classes ConverterDolar e ConverterEuro , esses objetos nos permitem utilizar os métodos de suas classes dentro da classe cambio , ou qualquer outra classe que os possuir.

 
Utilização da composição dentro da classe Cambio

Deixando mais clara a composição:

-A classe Cambio não possui o método "real_to_euro", para utlizarmos esse método dentro da classe cambio escrevemos o seguinte código dentro do seu escopo:

ConverterEuro euro = new ConverterEuro();

-Com o objeto "euro" criado ,agora temos acesso a todos os métodos de sua classe e para acessarmos seus métodos , utilizamos a palavra reservada "this", pois o objeto "euro" é um atributo da classe Cambio , e o "this" nos permite acessar esse atributo.

this.euro.real_to_euro();


3-executando o código

Vemos que a classe Cambio funciona perfeitamente

 
Execução da classe Cambio

Abaixo podemos ver que a classes Cambio consegue acessar todos os métodos das classes envolvidas na composição


Comparação entre o Mixins e a composição: Os dois possuem a mesma funcionalidade no caso apresentado, entretanto se utilizarmos muitas classes em java a composição vai ficar muito grande pois deveremos ficar instanciando vários objetos dentro da classe que queremos utilizar os métodos , alem de que o código ficaria muito complicado , em ruby temos a simplificação desse processo.

Namespace

editar

Descrição da Funcionalidade

editar

O crescimento de um programa implica nos seguintes problemas:

  • Quanto maior a quantidade de código existente, mais difícil a sua compreensão.
  • Com uma grande quantidade de código, costumamos trabalhar com mais nomes (classes, métodos e dados nomeados). Isso pode acabar provocando um conflito entre dois ou mais nomes, e, consequentemente, gerar uma falha de desenvolvimento. Esse conflito é mais provável de acontecer quando o programa usa bibliotecas de terceiros.

Uma forma de corrigir conflitos de nomes é usar prefixos com algum tipo de qualificador ou um conjunto de qualificadores. Mais há uma desvantagem quanto a este procedimento, já que os nomes tendem a ficar muito extensos, o que faz com que os programadores percam mais tempo para digitar, ler e reler nomes extensos e incompreensíveis do que para desenvolver o próprio software. Assim, podemos concluir que este método não é escalonável (muito menos aconselhável).

Já, se usarmos namespaces, o conflito de nomes é resolvido. Cria-se um contêiner nomeado para outros identificadores, como classes. Com duas classes de mesmo nome, mas cada uma localizada em um namespace diferente, não haverá conflito entre elas.

NameSpace, resumidamente, é uma forma de organizar código que nos permite criar um contexto local às suas propriedades. Sendo utilizado principalmente para evitar colisões de diferentes recursos que possuem o mesmo nome.

Exemplos

editar

Exemplo Didático

editar

Este é um exemplo da formação simples do namespace com uma classe e, em seguida, da execução dos métodos de classe definidos na classe. Aqui, acessamos os sub-membros dentro do namespace e o operador “::” é usado. Também é chamado de operador de resolução constante.

 

Exemplo Realístico

editar

1. Criar um módulo: No exemplo abaixo possuímos duas classes de mesmo nome e com métodos de mesmo nome, caso precisemos utilizar o método das duas classes nós não vamos conseguir, pois a última classe declarada está sobrescrevendo a declaração da anterior. Para solucionar esse problema , utilizaremos o recurso namespace. Que nada mais é que a refatoração (reorganização) do código em módulos. Primeiro passo é a criação de um módulo para cada classe , cada um vai encapsular uma classe. O primeiro módulo será "ConverterDolar" e o segundo "ConverterEuro".

 

Após a criação do módulo ConverterDolar, foi feita a criação do módulo ConverterEuro, que por sua vez possui uma classe também de nome Converter e dois métodos com os nomes: real_to_cambio e cambio_to_real. Esse método não possui muitas validações, pois a intenção é apenas demostrar o recurso de NameSpace.


2. Aplicar Namespaces: O próximo passo é instanciar um objeto do tipo da classe recém criada, e a partir dele chamar o método cambio_to_real. As classes também são constantes em ruby e por isso, é preciso utilizar o operador constant look para acessar aquelas que forem definidas dentro de módulos. Esse é o caso da classe Converter.

Exemplo em Java:

editar

1-Criando package e a nova classe: O namespace em java tem uma cara diferente, ao contrário do ruby que no próprio código se faz o namespace, em java você precisar criar um package(pacote) que nada mais é que uma pasta , para poder fazer o processo de namespace.

 
 


Comparação entre os namespaces: O namespace nada mais é do que a organização do código , vemos que em ruby temos a maior eficiência desse método pois dentro do próprio arquivo podemos criar um módulo que encapsula as classes assim podemos utilizar duas classes com mesmo nome sem o menor problema , em java ocorre a mesma coisa mas através de packages , entretanto para um programa grande e que exija grande organização a criação de vários packages ficara desgastante.

Todos os exemplos foram desenvolvidos no Repl, segue abaixo a lista:

  1. Exemplo Didático: Módulos
  2. Exemplo Didático: Mixins
  3. Exemplo Didático: Namespace
  4. Exemplo Realístico: Módulos
  5. Exemplo Realístico: Mixins
  6. Exemplo Realístico: Namespace
  7. Exemplo em Java: Módulos
  8. Exemplo em Java: Mixins
  9. Exemplo em Java: Namespace

Referências

editar

https://www.lume.ufrgs.br/bitstream/handle/10183/31036/000782127.pdf

https://medium.com/@sergiomaia/ruby-practice-modules-e8c29a5a0de8

https://www.ime.usp.br/~esposte/documents/aula-ruby/aula04/aula04.pdf

https://www.geeksforgeeks.org/namespaces-in-ruby/#:~:text=A%20namespace%20is%20a%20container,that%20names%20can%20be%20reused.




_________________________________________________________________________________________________________________________

Trabalho para a matéria Estrutura de Linguagens do curso de Ciência da Computação da Universidade do Estado do Rio de Janeiro com o tema de Mixins da linguagem de programação Ruby.

Desenvolvido pelos alunos Raquel Vieira Braga da Silva e Gustavo Moss de Oliveira Demétrio Ferreira.