Se sabe que en Ruby, los métodos de clase se heredan:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
Sin embargo, me sorprende que no funcione con mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
Sé que el método #extend puede hacer esto:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
Pero estoy escribiendo un mixin (o, más bien, me gustaría escribir) que contiene métodos de instancia y métodos de clase:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Ahora lo que me gustaría hacer es esto:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
Quiero que A, B herede los métodos de instancia y clase del Common
módulo. Pero, por supuesto, eso no funciona. Entonces, ¿no hay una forma secreta de hacer que esta herencia funcione desde un solo módulo?
Me parece poco elegante dividir esto en dos módulos diferentes, uno para incluir y el otro para ampliar. Otra posible solución sería utilizar una clase en Common
lugar de un módulo. Pero esto es solo una solución. (¿Qué pasa si hay dos conjuntos de funcionalidades comunes Common1
y Common2
realmente necesitamos tener mixins?) ¿Existe alguna razón profunda por la que la herencia del método de clase no funcione a partir de mixins?
Respuestas:
Un modismo común es usar
included
métodos de clase de enganche e inyección desde allí.fuente
include
agrega métodos de instancia,extend
agrega métodos de clase. Así es como funciona. No veo inconsistencias, solo expectativas noincluded
definición del método a otro módulo e incluir ESO en su módulo principal;)Aquí está la historia completa, explicando los conceptos de metaprogramación necesarios para comprender por qué la inclusión de módulos funciona de la manera en que lo hace en Ruby.
¿Qué sucede cuando se incluye un módulo?
La inclusión de un módulo en una clase agrega el módulo a los antepasados de la clase. Puede ver los antepasados de cualquier clase o módulo llamando a su
ancestors
método:Cuando llamas a un método en una instancia de
C
, Ruby buscará en cada elemento de esta lista de ancestros para encontrar un método de instancia con el nombre proporcionado. Dado que incluimosM
enC
,M
ahora es un antepasado deC
, por lo que cuando invocamosfoo
una instancia deC
, Ruby encontrará ese método enM
:Tenga en cuenta que la inclusión no copia ningún método de instancia o clase a la clase ; simplemente agrega una "nota" a la clase de que también debe buscar métodos de instancia en el módulo incluido.
¿Qué pasa con los métodos de "clase" en nuestro módulo?
Debido a que la inclusión solo cambia la forma en que se distribuyen los métodos de instancia, incluir un módulo en una clase solo hace que sus métodos de instancia estén disponibles en esa clase. Los métodos de "clase" y otras declaraciones del módulo no se copian automáticamente en la clase:
¿Cómo implementa Ruby los métodos de clase?
En Ruby, las clases y los módulos son objetos simples: son instancias de la clase
Class
yModule
. Esto significa que puede crear dinámicamente nuevas clases, asignarlas a variables, etc .:También en Ruby, tiene la posibilidad de definir los llamados métodos singleton en objetos. Estos métodos se agregan como nuevos métodos de instancia a la clase singleton oculta especial del objeto:
¿Pero no son las clases y los módulos simplemente objetos simples también? ¡De hecho lo son! ¿Eso significa que también pueden tener métodos singleton? ¡Sí, lo hace! Y así es como nacen los métodos de clase:
O bien, la forma más común de definir un método de clase es utilizarlo
self
dentro del bloque de definición de clase, que se refiere al objeto de clase que se está creando:¿Cómo incluyo los métodos de clase en un módulo?
Como acabamos de establecer, los métodos de clase son en realidad solo métodos de instancia en la clase singleton del objeto de clase. ¿Significa esto que podemos incluir un módulo en la clase singleton para agregar un montón de métodos de clase? ¡Sí, lo hace!
Esta
self.singleton_class.include M::ClassMethods
línea no se ve muy bien, así que Ruby agregóObject#extend
, que hace lo mismo, es decir, incluye un módulo en la clase singleton del objeto:Mover la
extend
llamada al móduloEste ejemplo anterior no es un código bien estructurado, por dos razones:
include
yextend
en laHostClass
definición para que nuestro módulo se incluya correctamente. Esto puede resultar muy engorroso si tiene que incluir muchos módulos similares.HostClass
referencias directasM::ClassMethods
, que es un detalle de implementación del móduloM
queHostClass
no debería ser necesario conocer o preocupar.Entonces, ¿qué tal esto? Cuando llamamos
include
en la primera línea, de alguna manera notificamos al módulo que se ha incluido, y también le damos nuestro objeto de clase, para que pueda llamarse aextend
sí mismo. De esta manera, es el trabajo del módulo agregar los métodos de clase si así lo desea.Para eso es exactamente el método especial
self.included
. Ruby llama automáticamente a este método siempre que el módulo se incluye en otra clase (o módulo) y pasa el objeto de la clase de host como primer argumento:Por supuesto, agregar métodos de clase no es lo único que podemos hacer
self.included
. Tenemos el objeto de clase, por lo que podemos llamar a cualquier otro método (clase) sobre él:fuente
Como mencionó Sergio en los comentarios, para los chicos que ya están en Rails (o que no les importa dependiendo de Active Support ),
Concern
es útil aquí:fuente
Puedes tener tu pastel y comértelo también haciendo esto:
Si tiene la intención de agregar variables de instancia y de clase, terminará tirando de su cabello ya que se encontrará con un montón de código roto a menos que lo haga de esta manera.
fuente