On sait que dans Ruby, les méthodes de classe sont héritées:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
Cependant, je suis surpris que cela ne fonctionne pas avec les mixins:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
Je sais que la méthode #extend peut faire ceci:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
Mais j'écris un mixin (ou, plutôt, j'aimerais écrire) contenant à la fois des méthodes d'instance et des méthodes de classe:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Maintenant, ce que je voudrais faire est ceci:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
Je veux que A, B héritent à la fois des méthodes d'instance et de classe du Common
module. Mais, bien sûr, cela ne fonctionne pas. Alors, n'y a-t-il pas un moyen secret de faire fonctionner cet héritage à partir d'un seul module?
Il me semble peu élégant de diviser cela en deux modules différents, l'un à inclure, l'autre à étendre. Une autre solution possible serait d'utiliser une classe Common
au lieu d'un module. Mais ce n'est qu'une solution de contournement. (Et s'il y a deux ensembles de fonctionnalités communes Common1
et Common2
que nous avons vraiment besoin de mixins?) Y a-t-il une raison profonde pour laquelle l'héritage de méthode de classe ne fonctionne pas à partir de mixins?
Réponses:
Un idiome courant est d'utiliser
included
des méthodes de hook et d'injecter des classes à partir de là.la source
include
ajoute des méthodes d'instance,extend
ajoute des méthodes de classe. Voilà comment cela fonctionne. Je ne vois pas d'incohérence, seulement des attentes non satisfaites :)included
définition de la méthode vers un autre module et inclure CELA dans votre module principal;)Voici l'histoire complète, expliquant les concepts de métaprogrammation nécessaires pour comprendre pourquoi l'inclusion de module fonctionne comme elle le fait dans Ruby.
Que se passe-t-il lorsqu'un module est inclus?
L'inclusion d'un module dans une classe ajoute le module aux ancêtres de la classe. Vous pouvez regarder les ancêtres de n'importe quelle classe ou module en appelant sa
ancestors
méthode:Lorsque vous appelez une méthode sur une instance de
C
, Ruby examinera chaque élément de cette liste d'ancêtres afin de trouver une méthode d'instance avec le nom fourni. Puisque nous avons inclusM
dansC
,M
est maintenant un ancêtre deC
, donc lorsque nous appelonsfoo
une instance deC
, Ruby trouvera cette méthode dansM
:Notez que l'inclusion ne copie aucune instance ou méthode de classe dans la classe - elle ajoute simplement une "note" à la classe indiquant qu'elle doit également rechercher des méthodes d'instance dans le module inclus.
Qu'en est-il des méthodes "class" de notre module?
Parce que l'inclusion ne change que la façon dont les méthodes d'instance sont distribuées, l'inclusion d'un module dans une classe ne rend ses méthodes d'instance disponibles que sur cette classe. Les méthodes "class" et autres déclarations du module ne sont pas automatiquement copiées dans la classe:
Comment Ruby implémente-t-il les méthodes de classe?
Dans Ruby, les classes et les modules sont des objets simples - ce sont des instances de la classe
Class
etModule
. Cela signifie que vous pouvez créer dynamiquement de nouvelles classes, les affecter à des variables, etc.:Aussi dans Ruby, vous avez la possibilité de définir des méthodes dites singleton sur des objets. Ces méthodes sont ajoutées en tant que nouvelles méthodes d'instance à la classe singleton spéciale et cachée de l'objet:
Mais les classes et les modules ne sont-ils pas également de simples objets? En fait, ils le sont! Cela signifie-t-il qu'ils peuvent également avoir des méthodes singleton? Oui! Et c'est ainsi que naissent les méthodes de classe:
Ou, la manière la plus courante de définir une méthode de classe est de l'utiliser
self
dans le bloc de définition de classe, qui fait référence à l'objet de classe en cours de création:Comment inclure les méthodes de classe dans un module?
Comme nous venons de l'établir, les méthodes de classe ne sont en réalité que des méthodes d'instance sur la classe singleton de l'objet de classe. Cela signifie-t-il que nous pouvons simplement inclure un module dans la classe singleton pour ajouter un tas de méthodes de classe? Oui!
Cette
self.singleton_class.include M::ClassMethods
ligne n'a pas l'air très jolie, donc Ruby a ajoutéObject#extend
, qui fait de même - c'est-à-dire inclut un module dans la classe singleton de l'objet:Déplacer l'
extend
appel dans le moduleCet exemple précédent n'est pas un code bien structuré, pour deux raisons:
include
etextend
dans laHostClass
définition pour que notre module soit correctement inclus. Cela peut devenir très fastidieux si vous devez inclure de nombreux modules similaires.HostClass
références directesM::ClassMethods
, qui est un détail de mise en œuvre du moduleM
quiHostClass
ne devrait pas avoir besoin de connaître ou de se soucier.Alors que diriez-vous de ceci: lorsque nous appelons
include
sur la première ligne, nous notifions en quelque sorte le module qu'il a été inclus, et lui donnons également notre objet de classe, afin qu'il puisse s'appelerextend
lui - même. De cette façon, c'est le travail du module d'ajouter les méthodes de classe s'il le souhaite.C'est exactement à cela que sert la méthode spéciale
self.included
. Ruby appelle automatiquement cette méthode chaque fois que le module est inclus dans une autre classe (ou module), et passe l'objet de classe hôte comme premier argument:Bien sûr, l'ajout de méthodes de classe n'est pas la seule chose que nous pouvons faire
self.included
. Nous avons l'objet de classe, nous pouvons donc appeler n'importe quelle autre méthode (de classe) dessus:la source
Comme Sergio l'a mentionné dans les commentaires, pour les gars qui sont déjà dans Rails (ou cela ne dérange pas selon support actif ),
Concern
est utile ici:la source
Vous pouvez avoir votre gâteau et le manger aussi en faisant ceci:
Si vous avez l'intention d'ajouter des variables d'instance et de classe, vous finirez par vous arracher les cheveux car vous rencontrerez un tas de code cassé à moins que vous ne le fassiez de cette façon.
la source