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
endonde 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
endAqui, 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
endNo 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)
endToda 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)
#=> falseE 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.