Quelle est la différence entre inclure et étendre dans Ruby?

415

Je me concentre sur la métaprogrammation Ruby. Les mixin / modules parviennent toujours à me confondre.

  • include : mélange dans les méthodes de module spécifiées en tant que méthodes d' instance dans la classe cible
  • extend : mélange dans les méthodes de module spécifiées en tant que méthodes de classe dans la classe cible

Alors, la différence principale est-elle juste ou est-ce qu'un plus gros dragon se cache? par exemple

module ReusableModule
  def module_method
    puts "Module Method: Hi there!"
  end
end

class ClassThatIncludes
  include ReusableModule
end
class ClassThatExtends
  extend ReusableModule
end

puts "Include"
ClassThatIncludes.new.module_method       # "Module Method: Hi there!"
puts "Extend"
ClassThatExtends.module_method            # "Module Method: Hi there!"
Gishu
la source
Consultez également ce lien: juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby
Donato

Réponses:

249

Ce que vous avez dit est correct. Mais il y a plus que cela.

Si vous avez une classe Klazzet un module Mod, y compris Moddans Klazzdonne des instances d' Klazzaccès aux Modméthodes de. Ou vous pouvez étendre Klazzen Moddonnant à la classe l' Klazz accès aux Modméthodes de. Mais vous pouvez également étendre un objet arbitraire avec o.extend Mod. Dans ce cas, l'objet individuel obtient Modles méthodes de même si tous les autres objets de la même classe on'en ont pas.

domgblackwell
la source
324

extend - ajoute les méthodes et les constantes du module spécifié à la métaclasse cible (c'est-à-dire la classe singleton), par exemple

  • si vous appelez Klazz.extend(Mod), Klazz a maintenant les méthodes de Mod (comme méthodes de classe)
  • si vous appelez obj.extend(Mod), obj a maintenant les méthodes de Mod (comme méthodes d'instance), mais aucune autre instance de of obj.classn'a ajouté ces méthodes.
  • extend est une méthode publique

include - Par défaut, il se mélange dans les méthodes du module spécifié en tant que méthodes d'instance dans le module / classe cible. par exemple

  • si vous appelez class Klazz; include Mod; end;, maintenant toutes les instances de Klazz ont accès aux méthodes de Mod (en tant que méthodes d'instance)
  • include est une méthode privée, car elle est destinée à être appelée à partir de la classe / module conteneur.

Cependant , les modules remplacent très souvent includele comportement de 'en corrigeant la includedméthode par des singes . Ceci est très important dans le code Rails hérité. plus de détails de Yehuda Katz .

Plus de détails sur include, avec son comportement par défaut, en supposant que vous avez exécuté le code suivant

class Klazz
  include Mod
end
  • Si Mod est déjà inclus dans Klazz, ou l'un de ses ancêtres, l'instruction include n'a aucun effet
  • Il inclut également les constantes de Mod dans Klazz, tant qu'elles ne s'affrontent pas
  • Il donne accès à Klazz aux variables du module de Mod, par exemple @@fooou@@bar
  • déclenche ArgumentError s'il y a des inclusions cycliques
  • Attache le module en tant qu'ancêtre immédiat de l'appelant (c'est-à-dire qu'il ajoute Mod à Klazz.ancestors, mais Mod n'est pas ajouté à la chaîne de Klazz.superclass.superclass.superclass. Donc, appeler superKlazz # foo vérifiera Mod # foo avant de vérifier à la méthode foo de la véritable superclasse de Klazz. Voir la RubySpec pour plus de détails.).

Bien sûr, la documentation de base ruby est toujours le meilleur endroit où aller pour ces choses. Le projet RubySpec était également une ressource fantastique, car ils documentaient précisément la fonctionnalité.

John Douthat
la source
22
Je sais que c'est un article assez ancien, mais la clarté de la réponse ne m'a pas empêché de commenter. Merci beaucoup pour une belle explication.
MohamedSanaulla
2
@anwar Évidemment, mais maintenant je peux commenter et j'ai réussi à retrouver l'article. Il est disponible ici: aaronlasseigne.com/2012/01/17/explaining-include-and-extend et je pense toujours que le schéma rend la compréhension beaucoup plus facile
systho
1
Le grand avantage de cette réponse est de savoir comment extendappliquer des méthodes en tant que méthodes de classe ou d' instance, selon l'utilisation. Klass.extend= méthodes de classe, objekt.extend= méthodes d'instance. J'ai toujours (à tort) supposé que les méthodes de classe provenaient extendet que l'instance provenait de include.
Frank Koehl
16

C'est correct.

Dans les coulisses, include est en fait un alias pour append_features , qui (à partir des documents):

L'implémentation par défaut de Ruby consiste à ajouter les constantes, les méthodes et les variables de module de ce module à un module si ce module n'a pas déjà été ajouté à un module ou à l'un de ses ancêtres.

Toby Hede
la source
5

Lorsque vous includeun module dans une classe, les méthodes du module sont importées en tant que méthodes d'instance .

Cependant, lorsque vous extendinsérez un module dans une classe, les méthodes du module sont importées en tant que méthodes de classe .

Par exemple, si nous avons un module Module_testdéfini comme suit:

module Module_test
  def func
    puts "M - in module"
  end
end

Maintenant, pour le includemodule. Si nous définissons la classe Acomme suit:

class A
  include Module_test
end

a = A.new
a.func

La sortie sera: M - in module.

Si nous remplaçons la ligne include Module_testavec extend Module_testet exécuter le code à nouveau, nous recevons l'erreur suivante: undefined method 'func' for #<A:instance_num> (NoMethodError).

Modification de l'appel de méthode a.funcpour A.func, la sortie passe à: M - in module.

De l'exécution du code ci-dessus, il est clair que lorsque nous includeun module, ses méthodes deviennent des méthodes d'instance et quand nous extendun module, ses méthodes deviennent des méthodes de classe .

Chintan
la source
3

Toutes les autres réponses sont bonnes, y compris l'astuce pour fouiller dans RubySpecs:

https://github.com/rubyspec/rubyspec/blob/master/core/module/include_spec.rb

https://github.com/rubyspec/rubyspec/blob/master/core/module/extend_object_spec.rb

Quant aux cas d'utilisation:

Si vous incluez le module ReusableModule dans la classe ClassThatIncludes, les méthodes, constantes, classes, sous-modules et autres déclarations sont référencées.

Si vous prolongez ClassThatExtends de classe avec le module ReusableModule, puis les méthodes et les constantes obtient copiées . Évidemment, si vous ne faites pas attention, vous pouvez gaspiller beaucoup de mémoire en dupliquant dynamiquement les définitions.

Si vous utilisez ActiveSupport :: Concern, la fonctionnalité .included () vous permet de réécrire directement la classe incluse. Le module ClassMethods à l'intérieur d'une préoccupation est étendu (copié) dans la classe incluse.

Ho-Sheng Hsiao
la source
1

Je voudrais également expliquer le mécanisme tel qu'il fonctionne. Si je n'ai pas raison, veuillez corriger.

Lorsque nous utilisons, includenous ajoutons un lien de notre classe à un module qui contient certaines méthodes.

class A
include MyMOd
end

a = A.new
a.some_method

Les objets n'ont pas de méthodes, seuls les clases et les modules en ont. Ainsi, lorsque areçoit un message, some_methodil commence la méthode de recherche some_methoddans ala classe propre de, puis dans la Aclasse et ensuite dans Ales modules de classe s'il y en a (dans l'ordre inverse, les dernières victoires incluses).

Lorsque nous utilisons, extendnous ajoutons une liaison à un module dans la classe propre de l'objet. Donc, si nous utilisons A.new.extend (MyMod), nous ajoutons un lien à notre module à la classe ou à la a'classe propre de l'instance de A. Et si nous utilisons A.extend (MyMod), nous ajoutons une liaison à la classe propre A (les objets, les classes sont également des objets) A'.

le chemin de recherche de méthode aest donc le suivant: a => a '=> modules liés à une' class => A.

il existe également une méthode de pré-ajout qui modifie le chemin de recherche:

a => a '=> modules ajoutés à A => A => module inclus à A

Désolé pour mon mauvais anglais.

user1136228
la source