¿Cuál es la diferencia entre include y extender en Ruby?

415

Solo estoy entendiendo la metaprogramación de Ruby. Los módulos / mixin siempre logran confundirme.

  • incluyen : mezclas en métodos de módulo especificados como métodos de instancia en la clase de destino
  • extender : se mezcla en métodos de módulo especificados como métodos de clase en la clase de destino

Entonces, ¿la diferencia principal es solo esto o hay un dragón más grande al acecho? p.ej

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
fuente
Mira este enlace también: juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby
Donato

Respuestas:

249

Lo que has dicho es correcto. Sin embargo, hay más que eso.

Si tiene una clase Klazzy un módulo Mod, incluso Moden Klazzda instancias de Klazzacceso a Modlos métodos de. O se puede extender Klazza Moddar la clase de Klazz acceso a los Modmétodos 's. Pero también puede extender un objeto arbitrario con o.extend Mod. En este caso, el objeto individual obtiene Modlos métodos aunque todos los demás objetos con la misma clase oque no.

domgblackwell
fuente
324

extender : agrega los métodos y constantes del módulo especificado a la metaclase del objetivo (es decir, la clase singleton), por ejemplo

  • si llamas Klazz.extend(Mod), ahora Klazz tiene los métodos de Mod (como métodos de clase)
  • si llama obj.extend(Mod), ahora obj tiene los métodos de Mod (como métodos de instancia), pero ninguna otra instancia de obj.classtiene esos métodos agregados.
  • extend es un método público

include : de forma predeterminada, se mezcla en los métodos del módulo especificado como métodos de instancia en el módulo / clase de destino. p.ej

  • si llama class Klazz; include Mod; end;, ahora todas las instancias de Klazz tienen acceso a los métodos de Mod (como métodos de instancia)
  • include es un método privado, porque está destinado a ser llamado desde la clase / módulo contenedor.

Sin embargo , los módulos a menudo anulan include el comportamiento de los monos parcheando el includedmétodo. Esto es muy prominente en el código de Rails heredado. más detalles de Yehuda Katz .

Más detalles sobre include, con su comportamiento predeterminado, suponiendo que haya ejecutado el siguiente código

class Klazz
  include Mod
end
  • Si Mod ya está incluido en Klazz, o uno de sus antepasados, la declaración de inclusión no tiene efecto
  • También incluye las constantes de Mod en Klazz, siempre que no choquen
  • Le da a Klazz acceso a las variables del módulo de Mod, por ejemplo, @@fooo@@bar
  • plantea ArgumentError si hay inclusiones cíclicas
  • Adjunta el módulo como el ancestro inmediato de la persona que llama (es decir, agrega Mod a Klazz.ancestors, pero Mod no se agrega a la cadena de Klazz.superclass.superclass.superclass. Entonces, llamando supera Klazz # foo verificará Mod # foo antes de verificar al método foo de la verdadera superclase de Klazz. Ver RubySpec para más detalles).

Por supuesto, la documentación de ruby ​​core es siempre el mejor lugar para estas cosas. El proyecto RubySpec también fue un recurso fantástico, porque documentaron la funcionalidad con precisión.

John Douthat
fuente
22
Sé que esta es una publicación bastante antigua, pero la claridad de la respuesta no pudo evitar que comentara. Muchas gracias por una buena explicación.
MohamedSanaulla
2
@anwar Obviamente, pero ahora puedo comentar y logré encontrar el artículo nuevamente. Está disponible aquí: aaronlasseigne.com/2012/01/17/explaining-include-and-extend y sigo pensando que el esquema hace que la comprensión sea mucho más fácil
systho
1
La gran victoria en esta respuesta es cómo extendpuede aplicar métodos como métodos de clase o instancia, dependiendo de la utilización. Klass.extend= métodos de clase, objekt.extend= métodos de instancia. Siempre (erróneamente) asumí que los métodos de clase provenían extend, y la instancia de include.
Frank Koehl
16

Eso es correcto.

Detrás de escena, include es en realidad un alias para append_features , que (de los documentos):

La implementación predeterminada de Ruby es agregar las constantes, los métodos y las variables de módulo de este módulo a un módulo si este módulo no se ha agregado a un módulo o uno de sus antepasados.

Toby Hede
fuente
5

Cuando ingresa includeun módulo en una clase, los métodos del módulo se importan como métodos de instancia .

Sin embargo, cuando extendconvierte un módulo en una clase, los métodos del módulo se importan como métodos de clase .

Por ejemplo, si tenemos un módulo Module_testdefinido de la siguiente manera:

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

Ahora, para el includemódulo. Si definimos la clase de la Asiguiente manera:

class A
  include Module_test
end

a = A.new
a.func

La salida será: M - in module.

Si reemplazamos la línea include Module_testcon extend Module_testy ejecute el código de nuevo, recibimos el siguiente error: undefined method 'func' for #<A:instance_num> (NoMethodError).

Cambio de la llamada al método a.funca A.func, la salida cambia a: M - in module.

De la ejecución del código anterior, está claro que cuando somos includeun módulo, sus métodos se convierten en métodos de instancia y cuando somos extendun módulo, sus métodos se convierten en métodos de clase .

Chintan
fuente
3

Todas las otras respuestas son buenas, incluida la sugerencia para explorar 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

En cuanto a los casos de uso:

Si incluye el módulo ReusableModule en la clase ClassThatIncludes, se hace referencia a los métodos, constantes, clases, submódulos y otras declaraciones.

Si extiende la clase ClassThatExtends con el módulo ReusableModule, los métodos y las constantes se copian . Obviamente, si no tiene cuidado, puede desperdiciar mucha memoria duplicando dinámicamente las definiciones.

Si usa ActiveSupport :: Concern, la funcionalidad .included () le permite reescribir la clase incluida directamente. El módulo ClassMethods dentro de una preocupación se extiende (copia) en la clase que lo incluye.

Ho-Sheng Hsiao
fuente
1

También me gustaría explicar el mecanismo a medida que funciona. Si no estoy en lo correcto, corrija.

Cuando usamos includeestamos agregando un enlace de nuestra clase a un módulo que contiene algunos métodos.

class A
include MyMOd
end

a = A.new
a.some_method

Los objetos no tienen métodos, solo las clases y los módulos los tienen. Entonces, cuando arecibe un mensaje some_method, comienza el método de búsqueda some_methoden ala clase eigen, luego en la Aclase y luego en Alos módulos vinculados a la clase si hay algunos (en orden inverso, las últimas victorias incluidas).

Cuando usamos extend, estamos agregando enlaces a un módulo en la clase eigen del objeto. Entonces, si usamos A.new.extend (MyMod) estamos agregando enlaces a nuestro módulo a la clase o a'clase de instancia de A. Y si usamos A.extend (MyMod) estamos agregando un enlace a la clase propia A (objeto, las clases también son objetos) A'.

entonces la ruta de búsqueda del método aes la siguiente: a => a '=> módulos vinculados a una' clase => A.

También hay un método de anteponer que cambia la ruta de búsqueda:

a => a '=> módulos anexados a A => A => módulo incluido a A

Perdón por mi mal ingles.

usuario1136228
fuente