Quand utiliser des classes imbriquées et des classes imbriquées dans des modules?

144

Je suis assez familier avec l'utilisation des sous-classes et des modules, mais plus récemment, j'ai vu des classes imbriquées comme celle-ci:

class Foo
  class Bar
    # do some useful things
  end
end

Ainsi que des classes imbriquées dans des modules comme ceci:

module Baz
  class Quux
    # more code
  end
end

Soit la documentation et les articles sont rares, soit je ne suis pas suffisamment informé sur le sujet pour chercher les bons termes de recherche, mais je n'arrive pas à trouver beaucoup d'informations sur le sujet.

Quelqu'un pourrait-il fournir des exemples ou des liens vers des articles expliquant pourquoi / quand ces techniques seraient utilisées?

cmhobbs
la source

Réponses:

140

D'autres langages OOP ont des classes internes qui ne peuvent pas être instanciées sans être liées à une classe de niveau supérieur. Par exemple, en Java,

class Car {
    class Wheel { }
}

seules les méthodes de la Carclasse peuvent créer des Wheels.

Ruby n'a pas ce comportement.

En rubis,

class Car
  class Wheel
  end
end

diffère de

class Car
end

class Wheel
end

seulement au nom de la classe Wheelvs Car::Wheel.. Cette différence de nom peut rendre explicite aux programmeurs que la Car::Wheelclasse ne peut représenter qu'une roue de voiture, par opposition à une roue générale. L'imbrication des définitions de classe dans Ruby est une question de préférence, mais elle sert un objectif en ce sens qu'elle applique plus fortement un contrat entre les deux classes et, ce faisant, transmet plus d'informations sur elles et leurs utilisations.

Mais pour l'interpréteur Ruby, ce n'est qu'une différence de nom.

En ce qui concerne votre deuxième observation, les classes imbriquées à l'intérieur des modules sont généralement utilisées pour l'espace de noms des classes. Par exemple:

module ActiveRecord
  class Base
  end
end

diffère de

module ActionMailer
  class Base
  end
end

Bien que ce ne soit pas la seule utilisation de classes imbriquées à l'intérieur de modules, c'est généralement la plus courante.

Pan Thomakos
la source
5
@rubyprince, je ne suis pas sûr de ce que vous entendez par l'établissement d'une relation entre Car.newet Car::Wheel.new. Vous n'avez certainement pas besoin d'initialiser un Carobjet pour initialiser un Car::Wheelobjet dans Ruby, mais la Carclasse doit être chargée et exécutée pour Car::Wheelêtre utilisable.
Pan Thomakos
30
@Pan, vous confondez les classes internes Java et les classes Ruby à espace de nom. Une classe imbriquée Java non statique est appelée classe interne et n'existe que dans une instance de la classe externe. Il existe un champ caché qui autorise les références externes. La classe interne Ruby est simplement espacée de noms et n'est en aucun cas "liée" à la classe englobante. C'est équivalent à une classe statique Java (imbriquée) . Oui, la réponse a beaucoup de voix mais elle n'est pas tout à fait exacte.
DigitalRoss
7
Je ne sais pas comment cette réponse a obtenu 60 votes positifs, et encore moins a été acceptée par le PO. Il n'y a littéralement pas une seule déclaration vraie ici. Ruby n'a pas de classes imbriquées comme Beta ou Newspeak. Il n'y a absolument aucune relation entre Caret Car::Wheel. Les modules (et donc les classes) sont simplement des espaces de noms pour les constantes, il n'y a pas de classe imbriquée ou de module imbriqué dans Ruby.
Jörg W Mittag
4
La seule différence entre les deux est la résolution constante (qui est lexicale, et donc évidemment différente car les deux extraits sont lexiquement différents). Il n'y a cependant absolument aucune différence entre les deux concernant les classes concernées. Il existe simplement deux classes totalement indépendantes. Période. Ruby n'a pas de classes imbriquées / internes. Votre définition de classes imbriquées est correct, mais il ne s'applique tout simplement pas Ruby, comme vous pouvez tester trivialement: Car::Wheel.new. Boom. Je viens de construire un Wheelobjet qui n'est pas imbriqué dans un Carobjet.
Jörg W Mittag
10
Ce message est très trompeur sans lire l'intégralité du fil de commentaires.
Nathan
50

Dans Ruby, la définition d'une classe imbriquée est similaire à la définition d'une classe dans un module. Cela ne force pas réellement une association entre les classes, cela crée juste un espace de noms pour les constantes. (Les noms de classe et de module sont des constantes.)

La réponse acceptée n'était correcte sur rien. 1 Dans l'exemple ci-dessous, je crée une instance de la classe lexicalement incluse sans qu'une instance de la classe englobante n'existe jamais.

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

Les avantages sont les mêmes que ceux des modules: encapsulation, regroupement du code utilisé à un seul endroit, et rapprochement du code de l'endroit où il est utilisé. Un grand projet peut avoir un module externe qui se produit à plusieurs reprises dans chaque fichier source et contient un grand nombre de définitions de classe. Lorsque les différents frameworks et codes de bibliothèque font tous cela, ils ne fournissent qu'un seul nom chacun au niveau supérieur, ce qui réduit les risques de conflits. Prosaic, bien sûr, mais c'est pourquoi ils sont utilisés.

L'utilisation d'une classe au lieu d'un module pour définir l'espace de noms externe peut avoir du sens dans un programme ou un script à un fichier, ou si vous utilisez déjà la classe de niveau supérieur pour quelque chose, ou si vous allez réellement ajouter du code pour lier les classes entre elles dans un vrai style de classe interne . Ruby n'a pas de classes internes mais rien ne vous empêche de créer le même comportement dans le code. Référencer les objets externes à partir des objets internes nécessitera toujours un pointage à partir de l'instance de l'objet externe, mais l'imbrication des classes suggérera que c'est ce que vous pourriez faire. Un programme soigneusement modulaire peut toujours créer les classes englobantes en premier, et elles peuvent raisonnablement être décomposées avec des classes imbriquées ou internes. Vous ne pouvez pas appeler newun module.

Vous pouvez utiliser le modèle général même pour les scripts, où l'espace de noms n'est pas terriblement nécessaire, juste pour le plaisir et la pratique ...

#!/usr/bin/env ruby

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

  def run
    ...
  end

  self
end.new.run
DigitalRoss
la source
15
S'il vous plaît, plutôt s'il vous plaît, n'appelez pas cela une classe intérieure. Ce n'est pas. La classe Bn'est pas à l' intérieur de la classe A. La constante B est nommée à l'intérieur de la classe A, mais il n'y a absolument aucune relation entre l'objet référencé par B(qui dans ce cas se trouve être juste une classe) et la classe référencée par A.
Jörg W Mittag
2
Ok, la terminologie "interne" a été supprimée. Bon point. Pour ceux qui ne suivent pas l'argument ci-dessus, la raison du différend est que lorsque vous faites quelque chose comme ça dans, par exemple, Java, les objets de la classe interne (et ici j'utilise le terme canoniquement) contiennent une référence à la classe externe et les variables d'instance externes peuvent être référencées par des méthodes de classe interne. Rien de tout cela ne se produit dans Ruby à moins que vous ne les liez avec du code. Et vous savez, si ce code était présent dans la classe, ahem, englobante, alors je parie que vous pourriez raisonnablement appeler Bar une classe interne.
DigitalRoss
1
Pour moi, c'est cette déclaration qui m'aide le plus lorsque je décide entre un module et une classe: You can't call new on a module.- donc en termes simples si je veux juste mettre en espace des noms de classes et ne jamais avoir besoin de créer une instance de la "classe" externe, alors J'utiliserais un module externe. Mais si je veux instancier une instance de la «classe» enveloppante / externe, alors j'en ferais une classe au lieu d'un module. Au moins, cela a du sens pour moi.
FireDragon
@FireDragon Ou un autre cas d'utilisation peut être que vous voulez une classe qui est une fabrique, dont les sous-classes héritent, et votre fabrique est responsable de la création des instances des sous-classes. Dans cette situation, votre Factory ne peut pas être un module car vous ne pouvez pas hériter d'un module, c'est donc une classe parente que vous n'instanciez pas (et pouvez `` namespace '' c'est des enfants si vous le souhaitez, un peu comme un module)
rmcsharry
15

Vous souhaiterez probablement l'utiliser pour regrouper vos classes dans un module. Une sorte d'espace de noms.

par exemple, le gem Twitter utilise des espaces de noms pour y parvenir:

Twitter::Client.new

Twitter::Search.new

Donc, les deux Clientet les Searchclasses vivent sous le Twittermodule.

Si vous souhaitez vérifier les sources, le code des deux classes peut être trouvé ici et ici .

J'espère que cela t'aides!

Pablo Fernandez
la source
3
Lien mis à jour de la gemme Twitter: github.com/sferik/twitter/tree/master/lib/twitter
kode
6

Il y a encore une autre différence entre les classes imbriquées et les modules imbriqués dans Ruby avant la version 2.5 que d'autres réponses n'ont pas réussi à couvrir et qui, selon moi, doivent être mentionnées ici. C'est le processus de recherche.

En bref: en raison de la recherche constante de niveau supérieur dans Ruby avant la version 2.5, Ruby peut finir par rechercher votre classe imbriquée au mauvais endroit ( Objecten particulier) si vous utilisez des classes imbriquées.

Dans Ruby antérieur à 2.5:
Structure de classe imbriquée: supposons que vous ayez une classe X, avec une classe imbriquée Y, ou X::Y. Et puis vous avez une classe de haut niveau nommée également Y. Si X::Yn'est pas chargé, alors ce qui suit se produit lorsque vous appelez X::Y:

N'ayant pas trouvé Ydans X, Ruby essaiera de le rechercher dans les ancêtres de X. PuisqueXest une classe et non un module, elle a des ancêtres, parmi lesquels [Object, Kernel, BasicObject]. Donc, il essaie de chercher Ydans Object, où il le trouve avec succès.

Pourtant, c'est le plus haut niveau Yet non X::Y. Vous recevrez cet avertissement:

warning: toplevel constant Y referenced by X::Y


Structure de module imbriqué: Supposons que dans l'exemple précédent Xsoit un module et non une classe.

Un module n'a que lui-même comme ancêtre: X.ancestorsproduirait [X].

Dans ce cas, Ruby ne pourra pas rechercher Ydans l'un des ancêtres de Xet lancera un fichier NameError. Rails (ou tout autre framework avec chargement automatique) essaiera de se charger X::Yaprès cela.

Voir cet article pour plus d'informations: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a-class-as-a-namespace-in-rails-applications/

In Ruby 2.5:
Suppression de la recherche constante de niveau supérieur.
Vous pouvez utiliser des classes imbriquées sans craindre de rencontrer ce bogue.

Timur Nugmanov
la source
3

En plus des réponses précédentes: Le module en Ruby est une classe

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object
demas
la source
11
Vous seriez plus précis pour dire que «la classe dans Ruby est un module»!
Tim Diggins
2
Tout dans Ruby peut être un objet, mais appeler un module une classe ne semble pas correct: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Chad M
et nous arrivons ici à la définition de «est»
ahnbizcad
Notez que les modules ne peuvent pas être instanciés. C'est-à-dire qu'il n'est pas possible de créer des objets à partir d'un module. Ainsi, les modules, contrairement aux classes, n'ont pas de méthode new. Donc, bien que vous puissiez dire que les modules sont une classe (minuscules), ils ne sont pas la même chose qu'une classe (majuscules) :)
rmcsharry