É sabido que em Ruby, os métodos de classe são herdados:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
No entanto, é uma surpresa para mim que não funciona com mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
Eu sei que o método #extend pode fazer isso:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
Mas estou escrevendo um mixin (ou melhor, gostaria de escrever) contendo métodos de instância e métodos de classe:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Agora, o que eu gostaria de fazer é:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
Eu quero que A, B herdam métodos de instância e classe do Common
módulo. Mas, é claro, isso não funciona. Portanto, não existe uma maneira secreta de fazer essa herança funcionar a partir de um único módulo?
Parece-me deselegante dividir isso em dois módulos diferentes, um para incluir e outro para estender. Outra solução possível seria usar uma classe em Common
vez de um módulo. Mas esta é apenas uma solução alternativa. (E se houver dois conjuntos de funcionalidades comuns Common1
e Common2
realmente precisarmos de mixins?) Existe alguma razão profunda pela qual a herança de método de classe não funciona com mixins?
Respostas:
Um idioma comum é usar
included
métodos de classe de gancho e injeção a partir daí.fonte
include
adiciona métodos de instância,extend
adiciona métodos de classe. É assim que funciona. Não vejo inconsistência, apenas expectativas não atendidas :)included
definição do método para outro módulo e incluir ISSO em seu módulo principal;)Aqui está a história completa, explicando os conceitos de metaprogramação necessários para entender por que a inclusão de módulo funciona da maneira que funciona em Ruby.
O que acontece quando um módulo é incluído?
Incluir um módulo em uma classe adiciona o módulo aos ancestrais da classe. Você pode ver os ancestrais de qualquer classe ou módulo chamando seu
ancestors
método:Quando você chama um método em uma instância de
C
, Ruby vai olhar para cada item desta lista de ancestrais para encontrar um método de instância com o nome fornecido. Como incluímosM
emC
,M
agora é um ancestral deC
, então, quando chamarmosfoo
uma instância deC
, Ruby encontrará esse método emM
:Observe que a inclusão não copia nenhuma instância ou método de classe para a classe - ela apenas adiciona uma "nota" à classe de que também deve procurar métodos de instância no módulo incluído.
E os métodos de "classe" em nosso módulo?
Como a inclusão apenas muda a maneira como os métodos de instância são despachados, incluir um módulo em uma classe apenas torna seus métodos de instância disponíveis nessa classe. Os métodos de "classe" e outras declarações no módulo não são copiados automaticamente para a classe:
Como Ruby implementa métodos de classe?
Em Ruby, classes e módulos são objetos simples - são instâncias da classe
Class
eModule
. Isso significa que você pode criar dinamicamente novas classes, atribuí-las a variáveis, etc .:Também em Ruby, você tem a possibilidade de definir os chamados métodos singleton em objetos. Esses métodos são adicionados como novos métodos de instância à classe singleton especial oculta do objeto:
Mas as classes e os módulos também não são objetos simples? Na verdade, eles são! Isso significa que eles também podem ter métodos singleton? Sim! E é assim que os métodos de classe nascem:
Ou, a maneira mais comum de definir um método de classe é usar
self
dentro do bloco de definição de classe, que se refere ao objeto de classe que está sendo criado:Como faço para incluir os métodos de classe em um módulo?
Como acabamos de estabelecer, os métodos de classe são, na verdade, apenas métodos de instância na classe singleton do objeto de classe. Isso significa que podemos apenas incluir um módulo na classe singleton para adicionar vários métodos de classe? Sim!
Esta
self.singleton_class.include M::ClassMethods
linha não parece muito boa, então Ruby adicionouObject#extend
, que faz o mesmo - ou seja, inclui um módulo na classe singleton do objeto:Movendo a
extend
chamada para o móduloEste exemplo anterior não é um código bem estruturado, por dois motivos:
include
eextend
naHostClass
definição para que nosso módulo seja incluído corretamente. Isso pode ser muito complicado se você tiver que incluir muitos módulos semelhantes.HostClass
referências diretasM::ClassMethods
, que é um detalhe de implementação do móduloM
queHostClass
não deve ser necessário saber ou se preocupar.Então, que tal isto: quando chamamos
include
na primeira linha, de alguma forma notificamos o módulo que ele foi incluído, e também damos a ele nosso objeto de classe, para que ele possa chamar aextend
si mesmo. Dessa forma, é função do módulo adicionar os métodos da classe, se desejar.É exatamente para isso que serve o método especial
self.included
. Ruby chama automaticamente esse método sempre que o módulo é incluído em outra classe (ou módulo) e passa o objeto de classe host como o primeiro argumento:Claro, adicionar métodos de classe não é a única coisa que podemos fazer
self.included
. Temos o objeto de classe, portanto, podemos chamar qualquer outro método (classe) nele:fonte
Como o Sergio mencionou nos comentários, para caras que já estão no Rails (ou não se importam em depender do Active Support ),
Concern
é útil aqui:fonte
Você pode ter seu bolo e comê-lo fazendo o seguinte:
Se você pretende adicionar variáveis de instância e de classe, acabará perdendo o controle, pois irá se deparar com um monte de código quebrado, a menos que faça desta forma.
fonte