2009-08-15 5 views
113

In Ruby, da Sie mehrere Mixins einschließen können, aber nur eine Klasse erweitern, scheint es wie Mixins gegenüber Vererbung bevorzugt werden.Ruby Vererbung vs Mixins

Meine Frage: Wenn Sie Code schreiben, der erweitert/enthalten werden muss, um nützlich zu sein, warum sollten Sie es jemals zu einer Klasse machen? Oder anders gesagt, warum sollten Sie es nicht immer zu einem Modul machen?

Ich kann nur einen Grund denken, warum Sie eine Klasse wollen, und das ist, wenn Sie die Klasse instanziieren müssen. Im Fall von ActiveRecord :: Base wird es jedoch nie direkt instanziiert. Also sollte es nicht stattdessen ein Modul gewesen sein?

Antwort

156

ich nur lesen zu diesem Thema in The Well-Grounded Rubyist (großes Buch, übrigens). Der Autor hat einen besseren Job zu erklären, als würde ich so werde ich ihn zitieren:


Keine einzige Regel oder Formel immer in der richtigen Konstruktion führt. Aber es ist sinnvoll, einen paar Überlegungen im Auge zu behalten, wenn Sie machen Klasse-versus-Modul Entscheidungen:

  • Module haben keine Instanzen. Daraus folgt, dass Entitäten oder Dinge sind in der Regel am besten in Klassen modelliert, und Eigenschaften oder Eigenschaften von Entitäten oder Dinge sind am besten in Modulen eingekapselt. Entsprechend, wie in Abschnitt 4.1.1 angemerkt, sind die Klassen Namen Substantive, während Modulnamen oft Adjektive sind (Stack versus Stacklike).

  • Eine Klasse kann nur eine Superklasse haben, aber sie kann beliebig viele Module mischen. Wenn Sie Vererbung verwenden, geben Sie der Erstellung einer sinnvollen Oberklasse/Unterklasse Beziehung Priorität. Verwenden Sie nicht die einzige Oberklassenbeziehung einer Klasse zu , um die Klasse mit etwas auszurüsten, das sich als eine von mehreren Merkmalsgruppen herausstellen könnte.

diese Regeln in einem Beispiel zusammenfassend, hier ist das, was Sie nicht tun sollten:

module Vehicle 
... 
class SelfPropelling 
... 
class Truck < SelfPropelling 
    include Vehicle 
... 

Vielmehr sollten Sie dies tun:

module SelfPropelling 
... 
class Vehicle 
    include SelfPropelling 
... 
class Truck < Vehicle 
... 

Die zweite Version Modelle die Entitäten und Eigenschaften viel sauberer. Truck stammt vom Fahrzeug ab (was sinnvoll ist), während SelfPropelling eine Eigenschaft von Fahrzeugen ist (zumindest alle, die uns in diesem Modell der Welt interessieren) - eine Eigenschaft, die an Lastwagen weitergegeben wird, weil Truck ein Nachkomme ist oder spezialisierte Form des Fahrzeugs.

+5

+1 für die artikulierte Antwort –

+0

Große Antwort +1 –

+0

Das Beispiel zeigt es ordentlich - Truck IS A Vehicle - es gibt keinen Truck, der kein Fahrzeug wäre. –

-1

Im Moment denke ich über das template Entwurfsmuster nach. Es würde sich mit einem Modul nicht richtig anfühlen.

9

Mein Take: Module dienen zum Teilen von Verhalten, während Klassen zum Modellieren von Beziehungen zwischen Objekten dienen. Sie könnten technisch alles nur zu einer Instanz von Object machen und in beliebigen Modulen mischen, um die gewünschten Verhaltensweisen zu erhalten, aber das wäre ein schlechtes, willkürliches und eher unlesbares Design.

+0

Diese beantwortet die Frage auf direktem Weg: Vererbung eine bestimmte Organisationsstruktur erzwingt, dass Ihr Projekt machen kann lesbarer. – emery

9

Die Antwort auf Ihre Frage ist weitgehend kontextuelle. Bei der Auswertung der Pubb-Beobachtung wird die Wahl in erster Linie von der betrachteten Domäne bestimmt.

Und ja, Active enthalten sein sollte und nicht durch eine Unterklasse erweitert. Ein anderer ORM - datamapper - genau das erreicht!

4

Ich mag die Antwort von Andy Gaskell sehr - wollte nur hinzufügen, dass ja, ActiveRecord sollte keine Vererbung verwenden, sondern ein Modul hinzufügen, um das Verhalten (meist Persistenz) zu einem Modell/Klasse hinzuzufügen. ActiveRecord verwendet einfach das falsche Paradigma.

Aus dem gleichen Grund mag ich MongoId über MongoMapper sehr, weil es dem Entwickler die Möglichkeit gibt, Vererbung als eine Möglichkeit zu verwenden, um etwas Sinnvolles in der Problemdomäne zu modellieren.

Es ist traurig, dass so ziemlich niemand in der Rails-Gemeinschaft "Ruby-Vererbung" so verwendet, wie es verwendet werden soll - um Klassenhierarchien zu definieren, nicht nur um Verhalten hinzuzufügen.

38

Ich denke Mixins eine gute Idee, aber es gibt ein weiteres Problem hier, dass niemand erwähnt hat: Namespace-Kollisionen. Betrachte:

module A 
    HELLO = "hi" 
    def sayhi 
    puts HELLO 
    end 
end 

module B 
    HELLO = "you stink" 
    def sayhi 
    puts HELLO 
    end 
end 

class C 
    include A 
    include B 
end 

c = C.new 
c.sayhi 

Welcher gewinnt? In Ruby stellt sich heraus, dass es sich um das Letztere handelt, module B, weil Sie es nach module A aufgenommen haben. Jetzt ist es einfach, dieses Problem zu vermeiden: Stellen Sie sicher, dass alle Konstanten und Methoden von module A und module B sich in unwahrscheinlichen Namespaces befinden. Das Problem ist, dass der Compiler Sie nicht vor Kollisionen warnt.

Ich argumentiere, dass dieses Verhalten nicht zu großen Teams von Programmierern skalieren-- Sie sollten nicht davon ausgehen, dass die Person class C über jeden Namen im Geltungsbereich bekannt ist. Mit Ruby können Sie sogar eine Konstante oder Methode eines anderen Typs überschreiben. Ich bin mir nicht sicher, dass jemals als korrektes Verhalten betrachtet werden könnte.

+4

Dieses Verhalten ist in den meisten Umgebungen nicht akzeptabel –

+2

Dies ist ein weises Wort der Vorsicht. Erinnert an die mehrfache Vererbungsproblematik in C++. –

+1

Gibt es eine gute Milderung dafür? Dies scheint ein Grund dafür zu sein, warum die Python-Mehrfachvererbung eine überlegene Lösung ist (es wird nicht versucht, eine Sprache zu starten, sondern nur diese spezifische Funktion zu vergleichen). – Marcin