Module#extend — Entendendo o Singleton Class do Ruby

Roberto Cunha
4 min readOct 29, 2019

--

Existem 3 maneiras principais de importar código em uma classe Ruby ou Módulo: Module#include, Module#prepend e Module#extend. #include e #prepend tornam os métodos disponíveis através de um truque na cadeia de ancestrais de um objeto, dando ao Ruby novas instruções de como, onde e em qual ordem procurar pelos métodos da classe.

Module#extend faz algo totalmente diferente. Primeiro, vamos olhar o que acontece quando usamos #extend para importar um método de um módulo. Depois vamos abrir o capô e ver se conseguimos entender como isso acontece.

O que Module#extends faz?

De forma simples: Module#extends disponibiliza um método de módulo como um método de classe.

exemplo do uso do extend

Quando RobotMethods é incluído na classe Robot, o método desse módulo é acessível à nível de classe. Esses métodos, entretanto, não são acessíveis como métodos de instância, como seriam se tivéssemos utilizado #include ou #prepend.

Mas como? Para entender o que está acontecendo aqui, teremos que dar uma olhada no ‘method dispatch’ do Ruby e ter uma ideia sobre métodos de classe.

Method Dispatch do Ruby

A primeira pergunta que devemos fazer é ’Em que lugar os métodos vivem?’

Se você respondeu ‘na classe’, você está meio certo. Classes em Ruby definem métodos de instância, acessíveis em instâncias dessa classe.

Considere uma instância da classe Employee:

some_employee é uma instância de Employee

Aqui está como uma instância desse objeto é, na verdade, representado na memória.

some_employee não sabe que ele tem um método chamado work(). Tudo o que ele sabe é que ele é membro de uma classe Employee através do ponteiro *class*. Quando alguém chama o método work() em some_employee, o Ruby segue a referência de classe e depois procura por um método igual onde ele é definido.

Faz sentido, certo? Desse modo, instâncias de cada classe não precisam carregar cópias de cada método de instância. Essa é uma boa forma de economizar memória no sistema.

Assim, está tudo bem com métodos de instância. Mas e com métodos de classe?

Métodos de classe — Um segredo terrível

Nessa nossa jornada, você percebeu que existe um Module#instance_methods mas não existe um Module#class_method. E existe uma boa razão para isso, mas talvez você não esteja pronto para escutar.

Respire fundo, jovem padawan. Está sentado, confortável? Que bom. Não existe método de class no Ruby, apenas métodos de instância que parecem e se comportam como métodos de classe.

Quando chamamos o método de classe validate_id!, Ruby seguirá o mesmo fluxo feito anteriormente.

Nós procuraremos na classe Employee um método de instância (validate_id!). Quando não encontrarmos, nós seguiremos o ponteiro *class* de referência até a classe Class e procuraremos por esse método lá. Employee é, afinal, uma instância da classe Class. (Tudo no Ruby é uma classe, lembra?)

Mas como assim? Se nós encontrarmos validade_id! na classe Class, então esse método deverá estar disponível para todas as classes, não apenas Employee. Mas o que, afinal, está acontecendo?

Classes Singleton do Ruby

Você provavelmente viu esse truque antes:

Exemplo de um método de instância

Nós chamamos esse método de método singleton. Esses métodos vivem em um objeto chamado singleton class, que, na verdade, vive de forma invisível entre o objeto em si e sua classe. Vamos provar?

Dê uma olhada:

Exemplo de método do singleton class

Faz sentido, certo? Se a definição do método estava na classe Employee, então não poderia ser de uma instância específica.

Então o que é um método de classe?

Um método de classe é um método que vive em um singleton class.

Vamos explicar essa afirmação.

Quando fazemos uma chamada à Employee.validate_id!, nós estamos invocando a classe Employee, que é, na verdade, uma instância da classe Ruby Class. Quando a classe Employee é criada, uma classe singleton que é criada ao mesmo tempo, contém o método validate_id!.

Dê uma olhada nesse exemplo para provar:

Exemplo do método singleton da classe Employee

Resumindo: métodos de classe não são métodos que são definidos no nível da classe de um objeto. São métodos que são definidos em uma classe singleton criada implicitamente no momento em que a classe é criada no sistema.

Finalmente: Como Module#extend funciona?

Quando chamamos Module#extend para importar métodos, não estamos trazendo esses métodos para dentro da instância. E muito menos para dentro da classe. Estamos, na verdade, trazendo eles para dentro do singleton class que ‘vive’ entre a classe criada e a classe Ruby Class, que ‘vive’ acima dessas classes.

Esse conhecimento não vital para desenvolvedores Ruby. Nem mesmo para a maioria dos desenvolvedores Ruby experientes. Mas é realmente útil saber se você utiliza #extend em vários lugares no seu código e sempre precisa debugar o comportamento de métodos estranhos.

Original/Créditos: https://medium.com/@jeremy_96642/module-extend-understanding-ruby-singleton-classes-9dea718c80f2

Estou iniciando um trabalho de tradução de diversos posts sobre desenvolvimento. Depois de algum tempo nesse ramo, percebi que existem muitos materiais ricos em conteúdo em inglês e, infelizmente, nem tantos em português.

Meu principal objetivo é que devs que não tiveram acesso ao aprendizado da língua inglesa ou estão aprendendo, possam se beneficiar desse conteúdo.

Meu Twitter: @_robertto

--

--