Wann sind verschachtelte Klassen und in Modulen verschachtelte Klassen zu verwenden?

144

Ich bin ziemlich vertraut mit der Verwendung von Unterklassen und Modulen, aber in jüngerer Zeit habe ich verschachtelte Klassen wie diese gesehen:

class Foo
  class Bar
    # do some useful things
  end
end

Sowie Klassen, die in Modulen wie diesen verschachtelt sind:

module Baz
  class Quux
    # more code
  end
end

Entweder sind Dokumentation und Artikel spärlich oder ich bin nicht ausreichend über das Thema informiert, um nach den richtigen Suchbegriffen zu suchen, aber ich kann anscheinend nicht viele Informationen zu diesem Thema finden.

Könnte jemand Beispiele oder Links zu Beiträgen bereitstellen, warum / wann diese Techniken verwendet werden?

cmhobbs
quelle

Antworten:

140

Andere OOP-Sprachen haben innere Klassen, die nicht instanziiert werden können, ohne an eine Klasse der oberen Ebene gebunden zu sein. Zum Beispiel in Java,

class Car {
    class Wheel { }
}

Nur Methoden in der CarKlasse können Wheels erstellen .

Ruby hat dieses Verhalten nicht.

In Ruby,

class Car
  class Wheel
  end
end

unterscheidet sich von

class Car
end

class Wheel
end

nur im Namen der Klasse Wheelvs. Car::Wheel. Dieser Namensunterschied kann Programmierern deutlich machen, dass die Car::WheelKlasse nur ein Autorad darstellen kann, im Gegensatz zu einem allgemeinen Rad. Das Verschachteln von Klassendefinitionen in Ruby ist eine Frage der Präferenz, dient jedoch einem Zweck in dem Sinne, dass ein Vertrag zwischen den beiden Klassen stärker durchgesetzt wird und auf diese Weise mehr Informationen über sie und ihre Verwendung vermittelt werden.

Für den Ruby-Interpreter ist es jedoch nur ein Unterschied im Namen.

Was Ihre zweite Beobachtung betrifft, werden Klassen, die in Modulen verschachtelt sind, im Allgemeinen verwendet, um die Klassen zu benennen. Zum Beispiel:

module ActiveRecord
  class Base
  end
end

unterscheidet sich von

module ActionMailer
  class Base
  end
end

Obwohl dies nicht die einzige Verwendung von Klassen ist, die in Modulen verschachtelt sind, ist dies im Allgemeinen die häufigste.

Pan Thomakos
quelle
5
@ Rubyprince, ich bin mir nicht sicher, was du damit meinst, eine Beziehung zwischen Car.newund herzustellen Car::Wheel.new. Sie müssen definitiv kein CarObjekt initialisieren, um ein Car::WheelObjekt in Ruby zu initialisieren , aber die CarKlasse muss geladen und ausgeführt werden, damit Car::Wheelsie verwendet werden kann.
Pan Thomakos
30
@Pan, Sie verwechseln Java- Innenklassen und Ruby-Klassen mit Namespace. Eine nicht statische verschachtelte Java-Klasse wird als innere Klasse bezeichnet und existiert nur innerhalb einer Instanz der äußeren Klasse. Es gibt ein verstecktes Feld, das nach außen gerichtete Referenzen zulässt. Die Ruby-innere Klasse hat einfach einen Namespace und ist in keiner Weise an die einschließende Klasse "gebunden". Es entspricht einer statischen (verschachtelten) Java- Klasse. Ja, die Antwort hat viele Stimmen, ist aber nicht ganz richtig.
DigitalRoss
7
Ich habe keine Ahnung, wie diese Antwort 60 positive Stimmen erhielt, geschweige denn vom OP akzeptiert wurde. Es gibt hier buchstäblich keine einzige wahre Aussage. Ruby hat keine verschachtelten Klassen wie Beta oder Newspeak. Es gibt absolut keine Beziehung zwischen Carund Car::Wheel. Module (und damit Klassen) sind einfach Namespaces für Konstanten. In Ruby gibt es keine verschachtelten Klassen oder verschachtelten Module.
Jörg W Mittag
4
Der einzige Unterschied zwischen den beiden ist die konstante Auflösung (die lexikalisch und daher offensichtlich unterschiedlich ist, da die beiden Schnipsel lexikalisch unterschiedlich sind). Es gibt jedoch absolut keinen Unterschied zwischen den beiden in Bezug auf die beteiligten Klassen. Es gibt einfach zwei völlig unabhängige Klassen. Zeitraum. Ruby hat keine verschachtelten / inneren Klassen. Ihre Definition von verschachtelten Klassen ist korrekt, gilt jedoch nicht für Ruby, da Sie trivial testen können : Car::Wheel.new. Boom. Ich habe gerade ein WheelObjekt erstellt, das nicht in einem CarObjekt verschachtelt ist .
Jörg W Mittag
10
Dieser Beitrag ist sehr irreführend, ohne den gesamten Kommentarthread zu lesen.
Nathan
50

In Ruby ähnelt das Definieren einer verschachtelten Klasse dem Definieren einer Klasse in einem Modul. Es erzwingt keine Zuordnung zwischen den Klassen, sondern erstellt lediglich einen Namespace für die Konstanten. (Klassen- und Modulnamen sind Konstanten.)

Die akzeptierte Antwort war in nichts richtig. 1 Im folgenden Beispiel erstelle ich eine Instanz der lexikalisch eingeschlossenen Klasse, ohne dass jemals eine Instanz der einschließenden Klasse existiert.

class A; class B; end; end
A::B.new

Die Vorteile sind dieselben wie für Module: Kapselung, Gruppierung von Code, der nur an einer Stelle verwendet wird, und Platzierung des Codes näher an dem Ort, an dem er verwendet wird. Ein großes Projekt verfügt möglicherweise über ein äußeres Modul, das in jeder Quelldatei immer wieder vorkommt und viele Klassendefinitionen enthält. Wenn die verschiedenen Frameworks und Bibliothekscodes dies alle tun, tragen sie jeweils nur einen Namen zur obersten Ebene bei, wodurch die Wahrscheinlichkeit von Konflikten verringert wird. Natürlich prosaisch, aber deshalb werden sie verwendet.

Die Verwendung einer Klasse anstelle eines Moduls zum Definieren des äußeren Namespace kann in einem Programm oder Skript mit einer Datei sinnvoll sein, oder wenn Sie die Klasse der obersten Ebene bereits für etwas verwenden oder wenn Sie tatsächlich Code hinzufügen, um die Klassen miteinander zu verknüpfen im wahren Stil der inneren Klasse . Ruby hat keine inneren Klassen, aber nichts hindert Sie daran, ungefähr dasselbe Verhalten im Code zu erzeugen. Wenn Sie die äußeren Objekte von den inneren Objekten referenzieren, müssen Sie weiterhin von der Instanz des äußeren Objekts aus punktieren. Wenn Sie jedoch die Klassen verschachteln, wird dies möglicherweise der Fall sein. Ein sorgfältig modularisiertes Programm kann immer zuerst die einschließenden Klassen erstellen und sie können vernünftigerweise mit verschachtelten oder inneren Klassen zerlegt werden. Sie können kein newModul aufrufen .

Sie können das allgemeine Muster auch für Skripte verwenden, bei denen der Namespace nicht unbedingt benötigt wird, nur zum Spaß und zum Üben ...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end

  self
end.new.run
DigitalRoss
quelle
15
Bitte, bitte schön, nenne das keine innere Klasse. Es ist nicht. Die Klasse Bist nicht innerhalb der Klasse A. Die Konstante B hat einen Namespace innerhalb der Klasse A, aber es gibt absolut keine Beziehung zwischen dem Objekt, auf das verwiesen wird B(was in diesem Fall zufällig eine Klasse ist) und der Klasse, auf die verwiesen wird A.
Jörg W Mittag
2
Ok, "innere" Terminologie entfernt. Guter Punkt. Für diejenigen, die dem obigen Argument nicht folgen, ist der Grund für den Streit, dass, wenn Sie so etwas beispielsweise in Java tun, Objekte der inneren Klasse (und hier verwende ich den Begriff kanonisch) einen Verweis auf enthalten Die Variablen der äußeren Klasse und der äußeren Instanz können durch Methoden der inneren Klasse referenziert werden. Nichts davon passiert in Ruby, es sei denn, Sie verknüpfen sie mit Code. Und Sie wissen, wenn dieser Code in der einschließenden Klasse vorhanden wäre, dann könnten Sie Bar vernünftigerweise eine innere Klasse nennen.
DigitalRoss
1
Für mich ist es diese Aussage, die mir bei der Entscheidung zwischen einem Modul und einer Klasse am meisten hilft: You can't call new on a module.- Wenn ich also nur einige Klassen mit einem Namespace versehen möchte und dann nie eine Instanz der äußeren "Klasse" erstellen muss Ich würde ein äußeres Modul verwenden. Aber wenn ich eine Instanz der umhüllenden / äußeren "Klasse" instanziieren möchte, würde ich sie zu einer Klasse anstelle eines Moduls machen. Zumindest macht das für mich Sinn.
FireDragon
@FireDragon Oder ein anderer Anwendungsfall könnte sein, dass Sie eine Klasse möchten, die eine Factory ist, von der Unterklassen erben, und Ihre Factory für das Erstellen von Instanzen der Unterklassen verantwortlich ist. In dieser Situation kann Ihre Factory kein Modul sein, da Sie nicht von einem Modul erben können. Es handelt sich also um eine übergeordnete Klasse, die Sie nicht instanziieren (und die
untergeordneten Elemente "benennen
15

Sie möchten dies wahrscheinlich verwenden, um Ihre Klassen in einem Modul zu gruppieren . Eine Art Namespace-Sache.

Das Twitter-Juwel verwendet beispielsweise Namespaces, um dies zu erreichen:

Twitter::Client.new

Twitter::Search.new

Also leben beide Clientund SearchKlassen unter dem TwitterModul.

Wenn Sie die Quellen überprüfen möchten, finden Sie den Code für beide Klassen hier und hier .

Hoffe das hilft!

Pablo Fernandez
quelle
3
Twitter gem aktualisierter Link: github.com/sferik/twitter/tree/master/lib/twitter
kode
6

Es gibt noch einen weiteren Unterschied zwischen verschachtelten Klassen und verschachtelten Modulen in Ruby vor 2.5, den andere Antworten nicht abdecken konnten, den ich hier erwähnen muss. Es ist der Suchprozess.

Kurz gesagt: Aufgrund der konstanten Suche auf oberster Ebene in Ruby vor 2.5 sucht Ruby möglicherweise ( Objectinsbesondere) an der falschen Stelle nach Ihrer verschachtelten Klasse, wenn Sie verschachtelte Klassen verwenden.

In Ruby vor 2.5:
Verschachtelte Klassenstruktur: Angenommen, Sie haben eine Klasse Xmit verschachtelter Klasse Yoder X::Y. Und dann haben Sie eine Top-Level-Klasse namens Y. Wenn X::Yes nicht geladen ist, geschieht Folgendes, wenn Sie anrufen X::Y:

Wenn Ruby nicht gefunden Ywurde X, versucht es, es in Vorfahren von nachzuschlagen X. Schon seitXist eine Klasse und kein Modul, es hat Vorfahren, darunter [Object, Kernel, BasicObject]. So versucht sie zu suchen , Yin Object, wo sie es erfolgreich findet.

Dennoch ist es die oberste Ebene Yund nicht X::Y. Sie erhalten diese Warnung:

warning: toplevel constant Y referenced by X::Y


Verschachtelte Modulstruktur: Angenommen, im vorherigen Beispiel Xhandelt es sich um ein Modul und nicht um eine Klasse.

Ein Modul hat nur sich selbst als Vorfahr: X.ancestorswürde produzieren [X].

In diesem Fall kann Ruby Ybei einem der Vorfahren von nicht suchen Xund wirft einen NameError. Rails (oder ein anderes Framework mit automatischem Laden) versuchen danach zu laden X::Y.

Weitere Informationen finden Sie in diesem Artikel: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

In Ruby 2.5:
Konstante Suche auf oberster Ebene entfernt.
Sie können verschachtelte Klassen verwenden, ohne befürchten zu müssen, dass dieser Fehler auftritt.

Timur Nugmanov
quelle
3

Zusätzlich zu den vorherigen Antworten: Modul in Ruby ist eine Klasse

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object
Demas
quelle
11
Sie würden genauer sagen, dass "Klasse in Ruby ein Modul ist"!
Tim Diggins
2
Alles in Ruby mag ein Objekt sein, aber das Aufrufen des Moduls als Klasse scheint nicht korrekt zu sein: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Objekt, Modul, Klasse]
Chad M
und hier kommen wir zur Definition von "ist"
ahnbizcad
Beachten Sie, dass Module nicht instanziiert werden können. Es ist also nicht möglich, Objekte aus einem Modul zu erstellen. Module haben also im Gegensatz zu Klassen keine Methode new. Obwohl Sie sagen können, dass Module eine Klasse (Kleinbuchstaben) sind, sind sie nicht dasselbe wie eine Klasse (Großbuchstaben) :)
rmcsharry