Ich habe gerade in The Well-Grounded Rubyist ( übrigens ein großartiges Buch) über dieses Thema gelesen . Der Autor erklärt es besser als ich, also zitiere ich ihn:
Keine einzelne Regel oder Formel führt immer zum richtigen Design. Es ist jedoch hilfreich, einige Überlegungen zu berücksichtigen, wenn Sie Entscheidungen zwischen Klasse und Modul treffen:
Module haben keine Instanzen. Daraus folgt, dass Entitäten oder Dinge im Allgemeinen am besten in Klassen modelliert werden und Merkmale oder Eigenschaften von Entitäten oder Dingen am besten in Modulen gekapselt werden. Entsprechend sind Klassennamen, wie in Abschnitt 4.1.1 erwähnt, in der Regel Substantive, während Modulnamen häufig Adjektive sind (Stack versus Stacklike).
Eine Klasse kann nur eine Oberklasse haben, aber sie kann so viele Module einmischen, wie sie möchte. Wenn Sie die Vererbung verwenden, sollten Sie vorrangig eine sinnvolle Beziehung zwischen Oberklasse und Unterklasse erstellen. Verwenden Sie nicht die einzige Superklassenbeziehung einer Klasse, um der Klasse etwas zu verleihen, das sich als nur eines von mehreren Merkmalen herausstellen könnte.
Wenn Sie diese Regeln in einem Beispiel zusammenfassen, sollten Sie Folgendes nicht tun:
module Vehicle
...
class SelfPropelling
...
class Truck < SelfPropelling
include Vehicle
...
Sie sollten dies vielmehr tun:
module SelfPropelling
...
class Vehicle
include SelfPropelling
...
class Truck < Vehicle
...
Die zweite Version modelliert die Entitäten und Eigenschaften viel übersichtlicher. Lkw stammt vom Fahrzeug ab (was sinnvoll ist), während SelfPropelling ein Merkmal von Fahrzeugen ist (zumindest alle, die uns in diesem Modell der Welt interessieren) - ein Merkmal, das an Lkw weitergegeben wird, weil Truck ein Nachkomme ist. oder spezielle Form des Fahrzeugs.
Truck
IS AVehicle
- es gibt keineTruck
, die keine wäreVehicle
. Allerdings würde ich Modul vielleicht nennenSelfPropelable
(:?) HmmSelfPropeled
klingt richtig, aber es ist fast das gleiche: D. Jedenfalls würde ich es nicht in,Vehicle
sondern in aufnehmenTruck
- da es Fahrzeuge gibt, die NICHT sindSelfPropeled
. Auch ein guter Hinweis ist zu fragen - gibt es andere Dinge, NICHT Fahrzeuge, die sindSelfPropeled
? - Na ja, aber ich wäre schwerer zu finden. SoVehicle
konnte von Klasse SelfPropelling erbt (als Klasse wäre es nicht so passenSelfPropeled
- wie das ist eher eine Rolle)Ich denke, Mixins sind eine großartige Idee, aber hier gibt es ein anderes Problem, das niemand erwähnt hat: Namespace-Kollisionen. Erwägen:
Welches gewinnt? In Ruby stellt sich heraus, dass es sich um Letzteres handelt
module B
, da Sie es danach eingefügt habenmodule A
. Jetzt ist es einfach, dieses Problem zu vermeiden: Stellen Sie sicher, dass sich alle Konstanten und Methoden vonmodule A
undmodule B
in unwahrscheinlichen Namespaces befinden. Das Problem ist, dass der Compiler Sie bei Kollisionen überhaupt nicht warnt.Ich behaupte, dass dieses Verhalten nicht auf große Teams von Programmierern skaliert werden kann - Sie sollten nicht davon ausgehen, dass die implementierende Person
class C
jeden Namen im Umfang kennt. Mit Ruby können Sie sogar eine Konstante oder Methode eines anderen Typs überschreiben . Ich bin mir nicht sicher, ob dies jemals als korrektes Verhalten angesehen werden kann.quelle
C#sayhi
AusgabenB::HELLO
nicht, weil Ruby die Konstanten verwechselt, sondern weil Ruby Konstanten von näher zu fern auflöst - also würde in immerHELLO
verwiesen in aufgelöst . Dies gilt auch dann, wenn die Klasse C auch eine eigene definiert hat .B
B::HELLO
C::HELLO
Mein Standpunkt: Module dienen zum Teilen von Verhalten, während Klassen zum Modellieren von Beziehungen zwischen Objekten dienen. Sie könnten technisch einfach alles zu einer Instanz von Object machen und die gewünschten Module einmischen, um die gewünschten Verhaltensweisen zu erzielen. Dies wäre jedoch ein schlechtes, zufälliges und eher unlesbares Design.
quelle
Die Antwort auf Ihre Frage ist weitgehend kontextbezogen. Nach der Beobachtung von Pubb wird die Wahl in erster Linie von der betrachteten Domäne bestimmt.
Und ja, ActiveRecord sollte enthalten sein und nicht um eine Unterklasse erweitert werden. Ein anderer ORM - Datamapper - erreicht genau das!
quelle
Die Antwort von Andy Gaskell gefällt mir sehr gut - ich wollte nur hinzufügen, dass ActiveRecord keine Vererbung verwenden sollte, sondern ein Modul zum Hinzufügen des Verhaltens (meistens Persistenz) zu einem Modell / einer Klasse. ActiveRecord verwendet einfach das falsche Paradigma.
Aus dem gleichen Grund mag ich MongoId gegenüber MongoMapper sehr, da der Entwickler die Möglichkeit hat, die Vererbung als Modellierungsmethode für etwas zu verwenden, das in der Problemdomäne von Bedeutung ist.
Es ist traurig, dass so gut wie niemand in der Rails-Community "Ruby-Vererbung" so verwendet, wie es verwendet werden soll - um Klassenhierarchien zu definieren, nicht nur um Verhalten hinzuzufügen.
quelle
Ich verstehe Mixins am besten als virtuelle Klassen. Mixins sind "virtuelle Klassen", die in die Ahnenkette einer Klasse oder eines Moduls eingefügt wurden.
Wenn wir "include" verwenden und ihm ein Modul übergeben, wird das Modul der Ahnenkette direkt vor der Klasse hinzugefügt, von der wir erben:
Jedes Objekt in Ruby hat auch eine Singleton-Klasse. Zu dieser Singleton-Klasse hinzugefügte Methoden können direkt für das Objekt aufgerufen werden und fungieren daher als "Klassen" -Methoden. Wenn wir für ein Objekt "verlängern" verwenden und dem Objekt ein Modul übergeben, fügen wir die Methoden des Moduls der Singleton-Klasse des Objekts hinzu:
Wir können mit der Methode singleton_class auf die Singleton-Klasse zugreifen:
Ruby bietet einige Hooks für Module, wenn diese in Klassen / Module gemischt werden.
included
ist eine von Ruby bereitgestellte Hook-Methode, die aufgerufen wird, wenn Sie ein Modul in ein Modul oder eine Klasse aufnehmen. Genau wie im Lieferumfang enthalten ist einextended
Haken zum Ausfahren. Es wird aufgerufen, wenn ein Modul um ein anderes Modul oder eine andere Klasse erweitert wird.Dies schafft ein interessantes Muster, das Entwickler verwenden könnten:
Wie Sie sehen können, fügt dieses einzelne Modul Instanzmethoden, "Klassen" -Methoden hinzu und wirkt direkt auf die Zielklasse (in diesem Fall Aufruf von a_class_method ()).
ActiveSupport :: Concern kapselt dieses Muster. Hier ist das gleiche Modul, das für die Verwendung von ActiveSupport :: Concern umgeschrieben wurde:
quelle
Im Moment denke ich über das
template
Designmuster nach. Mit einem Modul würde es sich einfach nicht richtig anfühlen.quelle