Ruby Object Model – Singleton Class


Leia em 2 minutos

No artigo anterior eu mostrei como o self muda em diferentes contextos. Neste artigo, você verá o que são as metaclasses e como elas podem ser úteis na metaprogramação.

Entendendo classes Singleton

Todo objeto do Ruby está associado a duas classes: a classe que a instanciou e uma classe anônima, escondida, específica do objeto. Esta classe anônima é chamada de Singleton Class, mas antes de ter um nome oficial também era chamada de anonymous class, metaclass, eigenclass ou ghost class.

O nome Singleton usado pelo Ruby nada tem a ver com o Singleton Pattern, que também está disponível com a biblioteca Singleton.

A sintaxe mais comum para acessar a classe Singleton é

class << object
end

onde object é o objeto cuja classe Singleton você quer. É muito comum vermos algo como o exemplo à seguir para definir métodos em uma classe.

class Person
  class << self
    def count
      @count ||= 0
    end
  end
end

Aqui, estamos definindo um método na classe Singleton do objeto Person (lembre-se: tudo no Ruby é objeto, inclusive classes). Como consequência, isso irá definir o método Person.count. O efeito é exatamente o mesmo que

class Person
  def self.count
    @count ||= 0
  end
end

No Ruby 1.9.2, foi adicionado o método Object#singleton_class, que é apenas um atalho para a sintaxe class << self; self; end. Em versões mais antigas, você pode injetar este método com

class Object
  def singleton_class
    class << self; self; end
  end unless respond_to?(:singleton_class)
end

Toda vez que injeta métodos em um objeto, eles são adicionados como métodos singleton. O que é realmente importante saber é que estes métodos pertecem unicamente ao objeto em que foram definidos, não afetando nenhum outro objeto da hieraquia.

string = "Hi there!"
another_string = "Hi there!"

def string.to_yo
  self.gsub(/\b(Hi|Hello)( there)\b?!?/, "Yo! Wassup?")
end

string.to_yo
#=> "Yo! Wassup?"

another_string.respond_to?(:to_yo)
#=> false

E para provar que o método to_yo é singleton, podemos utilizar o método Object#singleton_methods.

string.singleton_methods
#=> ["to_yo"]

another_string.singleton_methods
#=> []

Você também pode adicionar métodos singleton de outras maneiras. Uma delas é estendendo um objeto com um módulo.

module Extension
  def sum
    self.inject(0) {|sum, number| sum + number}
  end
end

numbers = [1,2,3]
numbers.extend Extension
numbers.singleton_methods
#=> ["sum"]

Outra maneira é usando a própria classe Singleton.

numbers = [1,2,3]

class << numbers
  def sum
    self.inject(0) {|sum, number| sum + number}
  end
end

numbers.singleton_methods
#=> ["sum"]

Ou ainda, executando código no contexto do objeto.

numbers = [1,2,3]

numbers.instance_eval do
  def sum
    self.inject(0) {|sum, number| sum + number}
  end
end

numbers.singleton_methods
#=> ["sum"]

Quando você cria uma classe Singleton em um objeto, não poderá mais utilizar o método Marshal.dump, já que a biblioteca Marshal não suporta objetos com classes Singleton (ela irá lançar a exceção TypeError: singleton can't be dumped). A única maneira de fazer isso e ainda poder utilizar o Marshal é utilizando o método Object#extend.

Agora, sabendo que você pode adicionar métodos em um objeto com uma sintaxe como def object.some_method; end, perceba que é exatamente isso que fazemos quando definimos um método em uma classe; a única diferença é que passamos o próprio self.

class Person
  def self.say_hello
    "Hello there!"
  end
end

Person.singleton_methods
#=> ["say_hello"]

Com base nesse exemplo, é possível afirmar que métodos de classe não existem no Ruby! Pelo menos não no sentido de métodos estáticos! O que acontece é que estes métodos pertencem a um objeto, que por acaso é uma classe.

Finalizando

No próximo artigo veremos mais sobre definição dinâmica e execução de métodos.