Je viens de lire sur ce sujet dans The Well-Grounded Rubyist (excellent livre, au fait). L'auteur fait un meilleur travail d'explication que moi, alors je vais le citer:
Aucune règle ou formule unique ne donne toujours la bonne conception. Mais il est utile de garder à l'esprit quelques considérations lorsque vous prenez des décisions de classe contre module:
Les modules n'ont pas d'instances. Il s'ensuit que les entités ou les choses sont généralement mieux modélisées dans des classes, et que les caractéristiques ou propriétés des entités ou des choses sont mieux encapsulées dans des modules. De même, comme indiqué dans la section 4.1.1, les noms de classe ont tendance à être des noms, alors que les noms de modules sont souvent des adjectifs (Stack versus Stacklike).
Une classe ne peut avoir qu'une seule superclasse, mais elle peut mélanger autant de modules qu'elle le souhaite. Si vous utilisez l'héritage, donnez la priorité à la création d'une relation superclasse / sous-classe sensible. N'utilisez pas la seule et unique relation de superclasse d'une classe pour doter la classe de ce qui pourrait s'avérer être l'un des nombreux ensembles de caractéristiques.
Pour résumer ces règles en un exemple, voici ce que vous ne devriez pas faire:
module Vehicle
...
class SelfPropelling
...
class Truck < SelfPropelling
include Vehicle
...
Vous devriez plutôt faire ceci:
module SelfPropelling
...
class Vehicle
include SelfPropelling
...
class Truck < Vehicle
...
La deuxième version modélise les entités et les propriétés de manière beaucoup plus précise. Le camion descend de Vehicle (ce qui a du sens), alors que SelfPropelling est une caractéristique des véhicules (du moins, tous ceux qui nous intéressent dans ce modèle du monde) - une caractéristique qui est transmise aux camions du fait que Truck est un descendant, ou forme spécialisée, de Véhicule.
Truck
EST AVehicle
- il n'y a aucunTruck
qui ne serait pas unVehicle
. Cependant, j'appellerais module peut-êtreSelfPropelable
(:?) HmmSelfPropeled
sonne bien, mais c'est presque la même chose: D. Quoi qu'il en soit, je ne l'inclurais pasVehicle
mais dansTruck
- car il y a des véhicules qui ne sont PASSelfPropeled
. Une bonne indication est également de demander - y a-t-il d'autres choses, PAS des véhicules qui SONTSelfPropeled
? - Eh bien peut-être, mais je serais plus difficile à trouver. DoncVehicle
pourrait hériter de la classe SelfPropelling (en tant que classe, cela ne rentrerait pas commeSelfPropeled
- car c'est plus un rôle)Je pense que les mixins sont une excellente idée, mais il y a un autre problème ici que personne n'a mentionné: les collisions d'espaces de noms. Considérer:
Lequel gagne? Dans Ruby, il s'avère que c'est ce dernier
module B
, parce que vous l'avez inclus aprèsmodule A
. Maintenant, il est facile d'éviter ce problème: assurez - vous que tousmodule A
etmodule B
les constantes et méthodes » sont peu probables namespaces. Le problème est que le compilateur ne vous avertit pas du tout lorsque des collisions se produisent.Je soutiens que ce comportement ne s'adapte pas à de grandes équipes de programmeurs - vous ne devriez pas supposer que la personne implémentant
class C
connaît chaque nom dans la portée. Ruby vous permettra même de remplacer une constante ou une méthode d'un type différent . Je ne sais pas qui pourrait jamais être considéré comme un comportement correct.la source
C#sayhi
sortiesB::HELLO
non pas parce que Ruby mélange les constantes, mais parce que ruby résout les constantes de plus près à plus éloignées - doncHELLO
référencé dansB
serait toujours résolu àB::HELLO
. Cela vaut même si la classe C a défini la sienneC::HELLO
aussi.Mon avis: les modules servent à partager le comportement, tandis que les classes servent à modéliser les relations entre les objets. Techniquement, vous pouvez simplement faire de tout une instance d'Object et mélanger les modules que vous souhaitez pour obtenir l'ensemble de comportements souhaité, mais ce serait une conception médiocre, aléatoire et plutôt illisible.
la source
La réponse à votre question est largement contextuelle. Distillant l'observation de pubb, le choix est principalement motivé par le domaine considéré.
Et oui, ActiveRecord aurait dû être inclus plutôt qu'étendu par une sous-classe. Un autre ORM - datamapper - y parvient précisément!
la source
J'aime beaucoup la réponse d'Andy Gaskell - je voulais juste ajouter que oui, ActiveRecord ne devrait pas utiliser l'héritage, mais plutôt inclure un module pour ajouter le comportement (principalement la persistance) à un modèle / classe. ActiveRecord utilise simplement le mauvais paradigme.
Pour la même raison, j'aime beaucoup MongoId par rapport à MongoMapper, car cela laisse au développeur la possibilité d'utiliser l'héritage comme moyen de modéliser quelque chose de significatif dans le domaine du problème.
Il est triste que pratiquement personne dans la communauté Rails n'utilise "l'héritage Ruby" comme il est censé être utilisé - pour définir les hiérarchies de classes, pas seulement pour ajouter un comportement.
la source
La meilleure façon dont je comprends les mixins sont les classes virtuelles. Les mixins sont des "classes virtuelles" qui ont été injectées dans la chaîne d'ancêtres d'une classe ou d'un module.
Lorsque nous utilisons "include" et lui passons un module, cela ajoute le module à la chaîne des ancêtres juste avant la classe dont nous héritons:
Chaque objet de Ruby a également une classe singleton. Les méthodes ajoutées à cette classe singleton peuvent être directement appelées sur l'objet et agissent donc comme des méthodes de «classe». Lorsque nous utilisons "extend" sur un objet et passons à l'objet un module, nous ajoutons les méthodes du module à la classe singleton de l'objet:
Nous pouvons accéder à la classe singleton avec la méthode singleton_class:
Ruby fournit des hooks pour les modules lorsqu'ils sont mélangés dans des classes / modules.
included
est une méthode hook fournie par Ruby qui est appelée chaque fois que vous incluez un module dans un module ou une classe. Tout comme inclus, il existe unextended
crochet associé pour l'extension. Il sera appelé lorsqu'un module est étendu par un autre module ou classe.Cela crée un modèle intéressant que les développeurs pourraient utiliser:
Comme vous pouvez le voir, ce module unique ajoute des méthodes d'instance, des méthodes "class", et agit directement sur la classe cible (appelant a_class_method () dans ce cas).
ActiveSupport :: Concern encapsule ce modèle. Voici le même module réécrit pour utiliser ActiveSupport :: Concern:
la source
En ce moment, je pense au
template
modèle de conception. Cela ne serait tout simplement pas correct avec un module.la source