Entendendo o self
e mixins
no Ruby
Um dos recursos mais confusos para quem está iniciando com Ruby é o funcionamento do self
.
Então o objetivo deste post é explicar seu funcionamento ao responder as seguintes perguntas: Qual problema o self resolve? Como ele funciona?
O self
O self
é uma palavra reservada do Ruby que dá acesso ao objeto atual.
Sendo que o objeto atual tem sempre haver com o contexto, escopo em que o mesmo é usado.
Contexto? Escopo?
Sim, contexto ou escopo tem haver com o momento em que o código é executado.
Falar é fácil… Mostre me código!
Veja nos comentários do código (#
) abaixo, o resultado que a execução irá gerar.
def quem_sou_eu
puts self.inspect
end
def mostre_minha_classe
puts self.class.inspect
end
quem_sou_eu
# main
mostre_minha_classe
# Object
Detalhes sobre o código acima:
- O método
puts
é responsável por imprimir o resultado no terminal - O método
.inspect
faz uma representação do objeto como string - O método
.class
retorna a classe que foi utilizada para gerar o objeto.
Perceba que no exemplo acima, o método quem_sou_eu
retornou main
, e o mostre_minha_classe
indica que esse main
é uma instância da classe Object
.
Observação: Esse
main
é o que chamamos de top level scope (escopo do nível superior), todo código Ruby executa a partir dele. O mesmo é gerado pelo próprio Ruby e tudo que for declarado nele estará disponível em qualquer outro ponto do código.
Dúvida: dá onde surgiu esse puts
?
Quando não for definido o objeto alvo do método, o Ruby usará o self
por padrão.
Como o puts
é acessível de qualquer objeto, perceba que o uso do self
para invocá-lo se torna opcional.
def quem_sou_eu_SEM_self_no_puts
puts self.inspect
end
def quem_sou_eu_USANDO_self_no_puts
self.puts self.inspect
end
quem_sou_eu_SEM_self_no_puts
# main
quem_sou_eu_USANDO_self_no_puts
# main
Legal né?
Experimento com métodos de instância
def quem_sou_eu
puts self.inspect
end
class Person
attr_reader :name
def initialize(name)
@name = name
end
def who_am_i
# Como disse em uma explicação mais acima,
# todo método declarado no top level scope (main)
# ficará acessível de qualquer ponto do código.
quem_sou_eu
end
end
quem_sou_eu
# main
person = Person.new('Serradura')
person.who_am_i
# #<Person:0x000000010297d9e0 @name="Serradura">
puts person.name
# Serradura
Antes de explicar o código acima, vamos relembrar o que é o self
:
É uma palavra reservada do Ruby que dá acesso ao objeto atual.
Veja que o quem_sou_eu
imprimiu main
e o person.who_am_i
que faz uso do quem_sou_eu
imprimiu #<Person:0x000000010297d9e0 @name="Serradura">
.
O código
0x000000010297d9e0
irá variar a cada execução e significa o endereço de memória do objeto.
Pode ser que você não soubesse que é possível acessar qualquer método declarado no main
, não é?
Mas assim… Declarar métodos no main
/ top level scope
para reusar em qualquer ponto do código é uma péssima prática!
Beleza, mas como reaproveitar métodos sem precisar usar essa estratégia?
Resposta: Através de mixins
.
Os mixins
A tradução to mix-in
é misturar. Logo, mixin é uma forma de misturar código! 😅
Beleza, mas como definir um mixin?
Resposta: Declarando um módulo (module
) e fazendo uso deles.
Fazendo mixins com include
module InspecionadorDeObjetos
def quem_sou_eu
puts self.inspect
end
def mostre_minha_classe
puts self.class.inspect
end
end
Agora vejamos como misturar esses métodos em outro escopo/contexto:
module InspecionadorDeObjetos
def quem_sou_eu
puts self.inspect
end
def mostre_minha_classe
puts self.class.inspect
end
end
class Person
include InspecionadorDeObjetos
attr_reader :name
def initialize(name)
@name = name
end
end
person = Person.new('Serradura')
person.quem_sou_eu
# #<Person:0x000000010297d9e0 @name="Serradura">
person.mostre_minha_classe
# Person
quem_sou_eu
# (NameError) undefined local variable or method `quem_sou_eu' for main:Object
Veja acima que o include InspecionadorDeObjetos
disponibiliza os métodos do módulo na instância da classe person.quem_sou_eu
.
Perceba também, que o quem_sou_eu
não está mais disponível no main
/top level scope
.
Nota: A partir de agora irei usar apenas
top level scope
para me referir aomain
, lembrando que um é sinônimo do outro.
Mas assim Serra, tem como disponibilizar os métodos main no top level scope
?
Tem sim! 😈
Fazendo mixins com extend
module InspecionadorDeObjetos
def quem_sou_eu
puts self.inspect
end
def mostre_minha_classe
puts self.class.inspect
end
end
class Person
include InspecionadorDeObjetos
attr_reader :name
def initialize(name)
@name = name
end
end
person = Person.new('Serradura')
person.quem_sou_eu
# #<Person:0x000000010297d9e0 @name="Serradura">
person.mostre_minha_classe
# Person
extend InspecionadorDeObjetos
quem_sou_eu
# main
mostre_minha_classe
# Object
Perceba que o método extend
tem a capacidade de modificar o contexto no qual o mesmo foi utilizado. Por conta disso, é possível invocar os métodos .quem_sou_eu
e .mostre_minha_classe
no top level scope após usarmos o extend InspecionadorDeObjetos
.
Beleza Serra, então tu tá querendo me dizer que posso usar o extend em qualquer objeto? Qualquer objeto mesmo?
Resposta: Sim!
module InspecionadorDeObjetos
def quem_sou_eu
puts self.inspect
end
def mostre_minha_classe
puts self.class.inspect
end
end
class Person
extend InspecionadorDeObjetos
end
Person.quem_sou_eu
# Person
Person.mostre_minha_classe
# Class
uma_string_qualquer = 'uma_string_qualquer'
uma_string_qualquer.extend(InspecionadorDeObjetos)
uma_string_qualquer.quem_sou_eu
# "uma_string_qualquer"
uma_string_qualquer.mostre_minha_classe
# String
'uma_outra_string_qualquer'.quem_sou_eu
# (NoMethodError) undefined method `quem_sou_eu' for "uma_outra_string_qualquer":String
Veja nos exemplos acima, que os métodos .quem_sou_eu
e .mostre_minha_classe
estão acessíveis para a classe Person
e para a "uma_string_qualquer"
. Porém, perceba que o extend
modificou o escopo do objeto em si e não de outros que tenham o mesmo tipo, como é o caso da string "uma_outra_string_qualquer"
que não tem o método .quem_sou_eu
.
Recap
O self
é:
- Uma palavra reservada do Ruby que dá acesso ao objeto atual.
- Invocado sempre que um método não tiver um objeto alvo. Ex:
puts 1
equivale aself.puts 1
include MyModule
equivale aself.include MyModule
extend MyModule
equivale aself.extend MyModule
O mixin
é:
- Uma forma de compartilhar métodos/comportamentos entre classes e objetos.
- Use
include SomeModule
para disponibilizar os métodos nas instâncias do objeto. - Use
extend SomeModule
para disponibilizar os métodos no escopo do objeto.
Conclusão
Espero que esse artigo te ajude melhor a entender o funcionamento do self
e como o self
/escopo dos objetos é respeitado mesmo com o uso de mixins
.
Gostou do conteúdo? Deixe seu comentário aqui embaixo contando o que achou. Valeu! 😉
Exercício extra
Execute o código abaixo e procure entender qual será o funcionamento do self
no uso com classes e herança.
class InspecionadorDeObjetos
def self.quem_sou_eu
puts self.inspect
end
def self.mostre_minha_classe
puts self.class.inspect
end
def quem_sou_eu
puts self.inspect
end
def mostre_minha_classe
puts self.class.inspect
end
end
class Person < InspecionadorDeObjetos
end
InspecionadorDeObjetos.quem_sou_eu
InspecionadorDeObjetos.mostre_minha_classe
InspecionadorDeObjetos.new.quem_sou_eu
InspecionadorDeObjetos.new.mostre_minha_classe
Person.new.quem_sou_eu
Person.new.mostre_minha_classe
Já ouviu falar do ada.rb - Arquitetura e Design de Aplicações em Ruby? É um grupo focado em práticas de engenharia de software com Ruby. Acesse o canal no telegram e junte-se a nós em nosso meetup mensal (100% on-line).
Comments