web-dev-qa-db-fra.com

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

Je sais très bien quand utiliser 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 tels que:

module Baz
  class Quux
    # more code
  end
end

La documentation et les articles sont rares ou je ne suis pas suffisamment renseigné sur le sujet pour trouver les termes de recherche appropriés, mais je n'arrive pas à trouver beaucoup d'informations sur le sujet.

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

132
cmhobbs

Other OOP) a classes internes qui ne peuvent ê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 classe Car peuvent créer 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 Wheel vs. Car::Wheel. Cette différence de nom peut expliquer aux programmeurs que le Car::Wheel La classe 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 a une fonction en ce sens qu'elle impose plus fortement un contrat entre les deux classes et transmet ainsi plus d'informations à leur sujet et à leur nature. les usages.

Mais pour l’interprète Ruby, ce n’est qu’une différence de nom.

En ce qui concerne votre deuxième observation, les classes imbriquées dans les modules sont généralement utilisées pour nommer les classes en espace. 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.

128
Pan Thomakos

En Ruby, définir une classe imbriquée est similaire à définir une classe dans un module. En réalité, il ne force pas l'association entre les classes, il crée simplement 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 à propos de rien.1 Dans l'exemple ci-dessous, je crée une instance de la classe lexicalement fermée sans qu'une instance de la classe englobante n'ait jamais existé.

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

Les avantages sont les mêmes que ceux des modules: encapsulation, code de regroupement utilisé à un seul endroit et placement du code plus près de l'endroit où il est utilisé. Un grand projet peut avoir un module externe qui se répète dans chaque fichier source et contient beaucoup de définitions de classe. Lorsque les divers cadres et codes de bibliothèque remplissent tous ces fonctions, ils ne contribuent qu’un seul nom 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 un sens dans un programme ou un script d'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. en vrai classe intérieure style. Ruby n'a pas de classes internes, mais rien ne vous empêche de créer à peu près le même comportement dans le code. Pour référencer les objets externes à partir des objets internes, il vous faudra tout de même être pointés de l'instance de l'objet externe, mais imbriquer les classes suggérera que c'est ce que vous êtes en train de faire. Un programme soigneusement modélisé peut toujours créer d'abord les classes englobantes, et elles peuvent être raisonnablement décomposées avec des classes imbriquées ou internes. Vous ne pouvez pas appeler new sur un module.

Vous pouvez utiliser le modèle général même pour les scripts, où l'espace de noms n'est pas vraiment 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
47
DigitalRoss

Vous voudrez probablement l'utiliser pour grouper vos classes dans un module. Une sorte de chose d'espace de noms.

par exemple, Twitter Twitter utilise les espaces de noms pour y parvenir:

Twitter::Client.new

Twitter::Search.new

Ainsi, les deux classes Client et Search résident dans le module Twitter.

Si vous voulez vérifier les sources, vous pouvez trouver le code des deux classes: ici et ici .

J'espère que cela t'aides!

15
Pablo Fernandez

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, mais d'autres réponses n'ont pas été couvertes, ce qui, à mon avis, doit être mentionné ici. Il s'agit du 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 chercher votre classe imbriquée dans le mauvais place (dans Object en particulier) si vous utilisez des classes imbriquées.

Dans Ruby avant 2.5:
Structure de classe imbriquée: Supposons que vous ayez une classe X, avec la classe imbriquée Y ou X::Y. Et puis vous avez une classe de niveau supérieur nommée aussi Y. Si X::Y n'est pas chargé, la suite se produit lorsque vous appelez X::Y:

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

Pourtant, il s’agit du niveau supérieur Y et non pas X::Y. Vous obtiendrez cet avertissement:

warning: toplevel constant Y referenced by X::Y


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

Un module a seulement comme ancêtre: X.ancestors produirait [X].

Dans ce cas, Ruby ne pourra pas rechercher Y dans l'un des ancêtres de X et jettera un NameError. Rails (ou tout autre framework avec autoloading) essaiera de charger X::Y après ça.

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 /

Dans Ruby 2.5:
La recherche constante de niveau supérieur a été supprimée.
Vous pouvez utiliser des classes imbriquées sans craindre de rencontrer ce bogue.

4
Tim N

En plus des réponses précédentes: Module in Ruby est une classe

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