Oi, espero que esteja pronto, porque agora iremos tentar entender o que é esse objeto "escondido" por todos os lugares quando programamos em Ruby.
Se você é um programador curioso, já sabe que Ruby é uma linguagem que utiliza o paradigma de Orientação à Objetos, mais que isso, em Ruby tudo é objeto, tudo mesmo. Quando definimos uma classe House
, por exemplo, essa classe também é um objeto, uma instância de Class
, podemos perceber isso com o trecho de código abaixo, onde chamamos o método .class
que retorna a classe de um objeto.
class House; end
# House é uma instância da classe `Class`
House.class # => Class
Isso pode ser diferente das coisas que você já viu antes e funciona de forma diferente de como eu acreditava, então começei a me perguntar como essa característica da linguagem me impactava como programador? Eu não tinha uma resposta muito boa e procurei entender mais sobre esse comportamento, até que encontrei a chamada Singleton Class (também encontrei referências que chamam Eigenclass, Classe anônima e Object-Specific Class), a responsável por termos em Ruby os chamados métodos de classe, como o desse trecho abaixo.
class House
def self.open
puts "...Opened"
end
end
House.open # => ...Opened
Acredito que não há nada no código acima que seja diferente do que estamos acostumados escrever como programador Ruby. Singleton Classes nos permitem adicionar métodos em objetos pré-definidos que só afetarão o objeto que define este método, como no trecho abaixo.
class House
def lawn_situation
puts "...this is perfect"
end
end
my_home = House.new
neighbors_house = House.new
def my_home.lawn_situation
puts "...could be better"
end
neighbors_house.lawn_situation # => ...this is perfect
my_home.lawn_situation # => ...could be better
Isso também explica como criamos o método de classe self.open
do trecho anterior, pois como a classe House
é um objeto da classe Class
, então também utilizamos essa sintáxe (self.open
) para adicionar um novo método nesse objeto pré-definido.
Entendi então que as sintaxes def self.open
e def my_home.lawn_situation
são responsáveis por acessar a Singleton Class desses objetos adicionandos estes métodos nelas e não diretamente nas suas classes, permitindo que estes novos métodos afetem apenas os seus respectivos objetos.
Acredito que nesse momento conseguimos compreender o que é esse objeto chamado Singleton Class e como utilizamos, mas ainda não discutimos sobre como ele nos impacta na vida real, pois faremos isso agora.
Em conversa com alguns colegas, foi citado que um possível uso para essa característica da linguagem seria sobreescrever métodos de objetos para auxiliar alguns testes. No exemplo a seguir, utilizamos a Singleton Class para sobreescrever um método com o objetivo de auxiliar o teste de um trecho código.
# Definindo a classe House
class House
def self.close
self.close_back_door
self.close_windows
self.close_principal_door
nil
end
def self.close_back_door = puts "...Closed Back Door"
def self.close_windows = puts "...Closed Windows"
def self.close_principal_door = puts "...Closed Principal Door"
end
# Testando a classe House
context "Error to close the house" do
def House.close_principal_door
raise EspecificException, "Error to close the principal door"
end
expect(House.close).to raise_error(EspecificException)
end
Particularmente, saber dessa característica de Ruby, me fez entender mais claramente um bug que criei ao desenvolver um comportamento, vou tentar apenas ilustrar o problema ocorrido em um contexto mais simples no trecho abaixo.
class Book
def self.gift_a_friend(friend, book)
@friend = friend
@book = book
return send_now(@book) unless friend_has_this_book?
puts "#{@friend.name} já possui o livro '#{@book}'"
end
private
def self.friend_has_this_book?
puts "Verificando se #{@friend.name} já possui o livro '#{@book}'"
@friend_has_this_book ||= @friend.books.include?(@book)
end
def self.send_now(book)
puts "Enviando o livro '#{book}' para #{@friend.name}!"
@friend.add_book(book)
end
end
class Friend
attr_reader :name, :books
def initialize(name, books = [])
@name = name
@books = books
end
def add_book(book)
@books << book
end
end
joao = Friend.new('João')
guilherme = Friend.new('Guilherme')
Book.gift_a_friend(joao, 'Lord of the Rings')
# Verificando se João já possui o livro 'Lord of the Rings'
# Enviando o livro 'Lord of the Rings' para João!
Book.gift_a_friend(joao, 'Lord of the Rings')
# Verificando se João já possui o livro 'Lord of the Rings'
# João já possui o livro 'Lord of the Rings'
Book.gift_a_friend(guilherme, 'Lord of the Rings')
# Verificando se Guilherme já possui o livro 'Lord of the Rings'
# Guilherme já possui o livro 'Lord of the Rings'
guilherme
# => #<Friend:0x000079bd4d8e6d20 @books=[], @name="Guilherme">
# WTF? Guilherme não possui o livro 'Lord of the Rings'
O problema é que, na segunda vez em que o método .friend_has_this_book?
for chamado, ele não vai executar @friend.books.include?(@book)
pois o valor @friend_has_this_book
já foi preenchido, pois a Singleton Class é criada apenas uma única vez (por isso o nome Singleton), as chamadas seguintes apenas acessarão a Singleton Class já existente mantendo seu estado.
Eu gostaria de ter entendido bem antes esse conceito da linguagem, mas é um ótimo ponto de partida pra quem quer conhecer mais profundamente a linguagem Ruby e suas características, com certeza esses entendimentos irão evitar a os erros e ajudá-lo na análise dos problemas que você enfrentará como programador.
Espero que tenha gostado, até a próxima.
Algumas fontes que eu utilizei para entender um pouco mais sobre esse assunto:
Top comments (0)