DEV Community

João
João

Posted on

O mistério do objeto de Ruby escondido Singleton Class

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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' 
Enter fullscreen mode Exit fullscreen mode

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)