Quando utilizzare le classi nidificate e le classi nidificate nei moduli?


144

Conosco abbastanza bene quando usare sottoclassi e moduli, ma più recentemente ho visto classi nidificate come questa:

class Foo
  class Bar
    # do some useful things
  end
end

Oltre alle classi nidificate in moduli come questo:

module Baz
  class Quux
    # more code
  end
end

O la documentazione e gli articoli sono scarsi o non sono abbastanza istruito sull'argomento da tentare i giusti termini di ricerca, ma non riesco a trovare molte informazioni sull'argomento.

Qualcuno potrebbe fornire esempi o collegamenti a post sul perché / quando tali tecniche dovrebbero essere utilizzate?

Risposte:


140

Altri linguaggi OOP hanno classi interne che non possono essere istanziate senza essere legate a una classe di livello superiore. Ad esempio, in Java,

class Car {
    class Wheel { }
}

solo i metodi nella Carclasse possono creare Wheels.

Ruby non ha questo comportamento.

In Ruby,

class Car
  class Wheel
  end
end

si differenzia da

class Car
end

class Wheel
end

solo in nome della classe Wheelcontro Car::Wheel. Questa differenza di nome può rendere esplicito ai programmatori che la Car::Wheelclasse può rappresentare solo una ruota di automobile, al contrario di una ruota generale. La nidificazione delle definizioni delle classi in Ruby è una questione di preferenza, ma ha uno scopo nel senso che impone più fortemente un contratto tra le due classi e nel farlo trasmette più informazioni su di loro e sui loro usi.

Ma per l'interprete Ruby, c'è solo una differenza nel nome.

Per quanto riguarda la tua seconda osservazione, le classi nidificate all'interno dei moduli vengono generalmente utilizzate per lo spazio dei nomi delle classi. Per esempio:

module ActiveRecord
  class Base
  end
end

si differenzia da

module ActionMailer
  class Base
  end
end

Sebbene questo non sia l'unico uso delle classi nidificate all'interno dei moduli, è generalmente il più comune.


5
@rubyprince, non sono sicuro di cosa intendi stabilendo una relazione tra Car.newe Car::Wheel.new. Sicuramente non è necessario inizializzare un Caroggetto per inizializzare un Car::Wheeloggetto in Ruby, ma la Carclasse deve essere caricata ed eseguita per Car::Wheelessere utilizzabile.
Pan Thomakos,

30
@Pan, stai confondendo le classi interne Java e le classi Ruby spaziali . Una classe nidificata Java non statica è chiamata classe interna ed esiste solo all'interno di un'istanza della classe esterna. C'è un campo nascosto che consente riferimenti esteriori. La classe interna di Ruby ha semplicemente lo spazio dei nomi e non è "legata" alla classe che la racchiude in alcun modo. È equivalente a una classe statica (nidificata) Java . Sì, la risposta ha molti voti ma non è del tutto accurata.
DigitalRoss

7
Non ho idea di come questa risposta abbia ottenuto 60 voti, e tanto meno sia stata accettata dall'OP. Non c'è letteralmente una sola vera affermazione qui. Ruby non ha classi nidificate come Beta o Newspeak. Non c'è assolutamente alcuna relazione tra Care Car::Wheel. I moduli (e quindi le classi) sono semplicemente spazi dei nomi per le costanti, in Ruby non esiste una classe nidificata o un modulo nidificato.
Jörg W Mittag,

4
L'unica differenza tra i due è la risoluzione costante (che è lessicale e quindi ovviamente diversa perché i due frammenti sono lessicamente diversi). Tuttavia, non vi è assolutamente alcuna differenza tra i due riguardo alle classi coinvolte. Esistono semplicemente due classi completamente non correlate. Periodo. Ruby non ha classi nidificate / interne. La tua definizione di classi nidificate è corretto, ma semplicemente non si applica a Ruby, come si può banalmente verificare: Car::Wheel.new. Boom. Ho appena costruito un Wheeloggetto che non è nidificato all'interno di un Caroggetto.
Jörg W Mittag,

10
Questo post è altamente fuorviante senza leggere l'intero thread di commenti.
Nathan,

50

In Ruby, la definizione di una classe nidificata è simile alla definizione di una classe in un modulo. In realtà non forza un'associazione tra le classi, crea semplicemente uno spazio dei nomi per le costanti. (I nomi di classe e modulo sono costanti.)

La risposta accettata non era corretta su nulla. 1 Nell'esempio seguente creo un'istanza della classe racchiusa in modo lessicale senza mai esistere un'istanza della classe acclusa.

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

I vantaggi sono gli stessi di quelli dei moduli: incapsulamento, raggruppamento del codice usato in un solo posto e posizionamento del codice più vicino al luogo in cui viene utilizzato. Un progetto di grandi dimensioni potrebbe avere un modulo esterno che si verifica ripetutamente in ogni file di origine e contiene molte definizioni di classe. Quando i vari framework e codici di libreria lo fanno tutti, contribuiscono con un solo nome ciascuno al livello più alto, riducendo la possibilità di conflitti. Prosaico, certo, ma è per questo che vengono usati.

L'utilizzo di una classe anziché di un modulo per definire lo spazio dei nomi esterno potrebbe avere senso in un programma o script a un file, oppure se si utilizza già la classe di livello superiore per qualcosa o se si intende effettivamente aggiungere codice per collegare le classi insieme nel vero stile di classe interiore . Ruby non ha classi interne ma nulla ti impedisce di creare lo stesso comportamento nel codice. Fare riferimento agli oggetti esterni da quelli interni richiederà comunque il puntamento dall'istanza dell'oggetto esterno, ma annidare le classi suggerirà che questo è ciò che potresti fare. Un programma attentamente modulare potrebbe sempre creare prima le classi che la racchiudono e potrebbero ragionevolmente essere scomposte con classi nidificate o interne. Non puoi chiamare newun modulo.

Puoi usare il modello generale anche per gli script, dove lo spazio dei nomi non è terribilmente necessario, solo per divertimento e pratica ...

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run

15
Per favore, abbastanza per favore, non chiamarlo classe interiore. Non è. La classe nonB è all'interno della classe . La costante si trova all'interno della classe , ma non esiste assolutamente alcuna relazione tra l'oggetto a cui fa riferimento (che in questo caso sembra essere una classe) e la classe a cui fa riferimento . A BABA
Jörg W Mittag,

2
Ok, la terminologia "interna" è stata rimossa. Buon punto. Per coloro che non seguono l'argomento sopra, la ragione della controversia è che quando fai qualcosa del genere in, diciamo, Java, gli oggetti della classe interna (e qui sto usando il termine canonicamente) contengono un riferimento a la classe esterna e le variabili dell'istanza esterna possono essere referenziate con metodi di classe interna. Niente di tutto ciò accade in Ruby a meno che tu non li colleghi al codice. E sai, se quel codice fosse presente nella classe che racchiude, quindi, scommetto che potresti ragionevolmente chiamare Bar una classe interna.
DigitalRoss

1
Per me è questa affermazione che mi aiuta di più nel decidere tra un modulo e una classe: You can't call new on a module.- quindi in termini di base se voglio solo spaziare alcune classi e non ho mai bisogno di creare effettivamente un'istanza della "classe" esterna, allora Userei un modulo esterno. Ma se voglio creare un'istanza di una "classe" avvolgente / esterna, la trasformerei in una Classe anziché in un Modulo. Almeno questo ha senso per me.
FireDragon,

@FireDragon Oppure un altro caso d'uso potrebbe essere quello di una classe che è una Factory da cui ereditano le sottoclassi e che la factory è responsabile della creazione di istanze delle sottoclassi. In quella situazione, la tua Fabbrica non può essere un modulo perché non puoi ereditare da un modulo, quindi è una classe genitore che non crei un'istanza (e puoi 'spazio dei nomi' è figli se vuoi, un po 'come un modulo)
rmcsharry

15

Probabilmente vuoi usarlo per raggruppare le tue classi in un modulo. Sorta di una cosa dello spazio dei nomi.

ad esempio la gemma di Twitter utilizza gli spazi dei nomi per raggiungere questo obiettivo:

Twitter::Client.new

Twitter::Search.new

Così sia Cliente Searchclassi di vivere sotto il Twittermodulo.

Se vuoi controllare le fonti, il codice per entrambe le classi può essere trovato qui e qui .

Spero che questo ti aiuti!


3
Link aggiornato gemma Twitter: github.com/sferik/twitter/tree/master/lib/twitter
kode

6

C'è ancora un'altra differenza tra le classi nidificate e i moduli nidificati in Ruby prima della 2.5 che altre risposte non sono riuscite a coprire che ritengo debbano essere menzionate qui. È il processo di ricerca.

In breve: a causa della costante ricerca di livello superiore in Ruby precedente alla 2.5, Ruby potrebbe finire per cercare la classe nidificata nel posto sbagliato ( Objectin particolare) se si utilizzano classi nidificate.

In Ruby prima della 2.5:
Struttura della classe nidificata: Supponiamo di avere una classe X, con classe nidificata Y, oppure X::Y. E poi hai anche una classe di livello superiore chiamata Y. Se X::Ynon è caricato, seguendo poi accade quando si chiama X::Y:

Non avendo trovato Yin XRuby cercherà di cercarlo nella antenati X. DaXè una classe e non un modulo, ha antenati, tra i quali lo sono [Object, Kernel, BasicObject]. Quindi, tenta di cercare Yin Object, dove trova con successo.

Eppure è il livello più alto Ye non X::Y. Riceverai questo avviso:

warning: toplevel constant Y referenced by X::Y


Struttura del modulo nidificato: supponiamo che nell'esempio precedente Xsia un modulo e non una classe.

Un modulo ha solo se stesso come antenato: X.ancestorsprodurrebbe [X].

In questo caso, Ruby non sarà in grado di cercare Yin uno degli antenati di Xe lancerà a NameError. Le rotaie (o qualsiasi altro framework con caricamento automatico) proveranno a caricarsi X::Ysuccessivamente.

Vedi questo articolo per maggiori informazioni: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

In Ruby 2.5:
Ricerca costante di livello superiore rimossa.
È possibile utilizzare le classi nidificate senza timore di riscontrare questo errore.


3

Oltre alle risposte precedenti: il modulo in Ruby è una classe

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object

11
Saresti più preciso nel dire che "la classe in Ruby è un modulo"!
Tim Diggins,

2
Tutto in Ruby può essere un oggetto, ma chiamare il modulo una classe non sembra corretto: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Chad M

e qui arriviamo alla definizione di "is"
ahnbizcad

Si noti che i moduli non possono essere istanziati. Vale a dire, non è possibile creare oggetti da un modulo. Quindi i moduli, a differenza delle classi, non hanno un metodo new. Quindi, sebbene si possa dire che i moduli sono una classe (minuscola), non sono la stessa cosa di una classe (maiuscola) :)
rmcsharry,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.