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.