Ruby Object Model – Singleton Class
30/09/10
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.
- Permalink
- Trackback
- Comentários (14)
- Ao som de: Jimmy Eat World – The Middle
Textos escritos por
Comentários #
[...] próximo artigo, irei falar sobre metaclasses do Ruby. Até [...]
[...] This post was mentioned on Twitter by tapajos, Ozéias Sant'ana, Rafael Rosa, Tino Gomes, Alberto Leal and others. Alberto Leal said: RT @ozeias: RT @fnando: segundo artigo sobre ruby object model, agora abordando as singleton classes do ruby. ~ http://bit.ly/d8GPXY [...]
Excelente esta série de artigos, está clareando bastante minha mente no aprendizado de Ruby.
@Nando existe alguma perda de performance nesses diferente modos de adicionar métodos singleton?
Nando uma sugestão bem legal, seria colocar exemplos reais dos conceitos apresentados, por exemplo como o rails ou outro framework implementa na pratica.
A ideia é agrupar todos os conceitos que estou mostrando quando eu começar a falar de Metaprogramming. Exemplos reais normalmente são compostos por mais de uma técnica, e isso dificultaria uma explicação mais focada e simples.
Boa pergunta. Não cheguei a fazer nenhum benchmark nesse sentido! Acredito que se houver, a diferença não deve ser tão gritante.
Nessa questão de performance, vale lembrar que classes singleton trazem todo o custo de declarar uma classe nova (consumo de memória, "poluição" dos caches de invocação de método) para beneficiar um único objeto. Internamente, na primeira vez que se requer a singleton, o Ruby apenas altera o ponteiro de classe do objeto para uma nova anônima que herda da classe original (mais ou menos como: `x.class = Class.new(x.class)`)
Sendo assim, de maneira geral não é uma boa idéia usar singleton classes em objetos "de massa", criar uma subclasse explícita é quase sempre uma idéia melhor. Para objetos que são instanciados apenas uma vez (ou seja, essencialmente singletons), como classes, não há diferença prática.
Legal, bem bacana esse "diferença" entre singletons de pattern.
Parabéns pelos posts.
Isso me lembra um post meu, antigo, falando sobre exatamente o mesmo assunto. Talvez esteja um pouco mais confuso, mas serve talvez pra ilustrar um pouco (tentei colocar uns exemplos lá):
http://mauricioszabo.wordpress.com/2010/06/01/o-que-e-eingenclass-afinal-2/
No mais, parabéns pelo post!
muitoooo bom
abraco
perfect
abraco
Muito bom!
Obrigado por compartilhar este conhecimento conosco!
[]'s
[...] falei sobre classes singleton no Ruby, mostrei como era possível atuar no contexto da classe. Uma coisa muito comum entre os [...]
Deixe um comentário