Metaprogramação: Eigenclass em Ruby
O modelo de objetos do Ruby não é trivial. Em códigos mais complexos não é simples saber de onde realmente vem um método ou mesmo enxergar que toda classe em Ruby é um objeto, e isso pode causar algumas confusões. Uma das partes mais interessantes do modelo de objetos Ruby é conhecida como Singleton Class. Para chegar nela, vamos analisar o código abaixo:
class Caelum def teste puts "teste" end end
obj = Caelum.new obj.teste
Nada demais até aqui. Apenas criamos uma classe e invocamos um método. Porém em Ruby nossas classes são abertas, e podemos adicionar novos métodos não só em classes já definidas, como também em objetos. Uma forma de fazer isso é:
def obj.ensina puts "ruby" end
obj.ensina # => 'ruby'
Basicamente um objeto procura os métodos na sua classe (obj procura em Caelum) e, caso não encontre, sobe na hierarquia das superclasses, até Object (ou BasicObject no Ruby 1.9).
Mas onde está o método ensina
na hierarquia? Ele não pode estar definido em obj
, afinal obj
não é uma classe, e também não pode estar na classe Caelum
, ou todas as instancias da classe Caelum
também teriam esse método.
Eigenclasses
O fluxo comentado acima não está totalmente completo. Cada objeto em Ruby pode ter sua própria classe, uma classe especial, escondida. Existe uma sintaxe especial para acessar essa classe, que é:
class << obj end
Qualquer método definido dentro do código acima pertenceria apenas aquela instância. A classe acima é chamada de Eigenclass (ou Singleton Class). O modelo de objetos Ruby agora fica da seguinte forma:
Eigenclasses e Method Lookup
Seguindo o modelo de objetos Ruby, um objeto procura um método de instância na sua Classe. Caso não ache, sobe para a superclasse da classe.
class Caelum def ensina
end end
class Sala < Caelum end
obj = Sala.new obj.ensina
No exemplo acima, obj
procura o método ensina
na classe Sala
. Como não encontra, sobe até a superclasse que é Caelum
. Onde entra a Eigenclass nisso ?
Primeiro vamos criar um método em todos os objetos que retorne a Eigenclass de um objeto:
class Object def eigenclass class << self; self; end end end
p "caelum".class #=> String p "caelum".eigenclass #=> #Class:#String:0x18ba78
Repare que o Ruby coloca um sinal # na frente da Eigenclass. Usaremos esse sinal apartir de agora para identificar esse tipo de classe, sendo #obj
a Eigenclass do objeto obj
.
Agora vamos criar um método em obj
colocando-o na sua Eigenclass e verificar a hierarquia:
class << obj def eigen\_method 'obj#eigen\_method()' end end
p obj.class #=> Sala p obj.eigenclass #=> #obj p obj.eigenclass.superclass #=> Sala
A superclasse de uma Eigenclass é a própria classe do objeto. Ou seja, se um objeto possui uma Eigenclass, Ruby inicia a procura do método nela. Caso não encontre, sobe para a primeira superclasse (que é a própria classe do objeto).
Eigenclasses e herança
Para completar o modelo, quem é a superclasse de uma Eigenclass? Vamos adicionar uma eigenclass na classe Caelum e analisar o modelo:
class Caelum class << self def meu\_eigen\_method 'Caelum.meu\_eigen\_method()' end end end
p Caelum.eigenclass # => #Caelum p Sala.eigenclass # => #Sala p Sala.eigenclass.superclass # => #Caelum p Caelum.eigenclass.superclass # => #Object
Repare que a Superclass da Eigenclass de Sala (Sala.eigenclass.superclass
) é a eigenclass da classe Caelum! Ou seja, A superclasse de uma eigenclasse é a eigenclass da superclasse do objeto! Não muito trivial.
Matz, o criador do Ruby não chegou a definir um nome para esse tipo de classe, sendo que os programadores costumaram chamá-la de singleton class por conta própria. Como muita gente acabou confundindo com o design pattern de mesmo nome, outras opções começaram a surgir. Eingenclass surgiu na Alemanha onde eigen tem o significado de "próprio de algo", sendo uma tradução comum algo como "a própria classe do objeto".