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


Estoy pensando en la metaprogramación de Ruby. Los mixin / módulos siempre logran confundirme.

  • include : mezcla en métodos de módulo especificados como métodos de instancia en la clase de destino
  • extend : mezcla en métodos de módulo especificados como métodos de clase en la clase de destino

Entonces, ¿la gran diferencia es solo esto o un dragón más grande está al acecho? por ejemplo,

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!"
Author: nbro, 2008-10-01

6 answers

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

Si tiene una clase Klazz y un módulo Mod, incluyendo Mod en Klazz da instancias de Klazz acceso a los métodos de Mod. O puedes extender Klazz con Mod dando la clase Klazz acceso a los métodos de Mod. Pero también puede extender un objeto arbitrario con o.extend Mod. En este caso, el objeto individual obtiene los métodos de Mod aunque todos los demás objetos con la misma clase que o no lo hacen.

 221
Author: domgblackwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-07 02:32:47

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

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

Include - Por defecto, se mezcla en el módulo especificado métodos como métodos de instancia en el módulo/clase de destino. por ejemplo,

  • si llama a 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 dentro de la clase/módulo contenedor.

Sin embargo , los módulos muy a menudo reemplazan include's comportamiento por mono-parcheando el método included. Esto es muy prominente en el código 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 instrucción include no tiene efecto
  • También incluye constantes de Mod en Klazz, siempre y cuando no choquen
  • Le da acceso a Klazz a las variables de módulo de Mod, por ejemplo, @@foo o @@bar
  • plantea ArgumentError si hay cíclico incluye
  • Adjunta el módulo como antepasado inmediato del llamante (es decir, añade Mod a Klazz.antepasados, pero Mod no se añade a la cadena de Klazz.superclase.superclase.superclase. Por lo tanto, llamando a super en Klazz#foo comprobará el Mod#foo antes de comprobar el método foo de la superclase real de Klazz. Vea el RubySpec para más detalles.).

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

 290
Author: John Douthat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-22 21:53:23

Eso es correcto.

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

La implementación predeterminada de Ruby es agregar las constantes, métodos y módulo variables de este módulo a aModule si este módulo aún no ha sido añadido a aModule o a uno de sus antepasados.

 13
Author: Toby Hede,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-01 08:08:30

Todas las otras respuestas son buenas, incluyendo la punta para cavar a través de 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 module ReusableModule en la clase ClassThatIncludes, se hace referencia a los métodos, constantes, clases, submódulos y otras declaraciones.

Si extiendes class ClassThatExtends with module ReusableModule, entonces los métodos y constantes se copian . Obviamente, si no tiene cuidado, puede desperdiciar mucha memoria duplicando dinámicamente las definiciones.

Si utiliza ActiveSupport:: Concern, el .la funcionalidad included () le permite reescribir la clase de inclusión directamente. module ClassMethods inside a Concern gets extended (copied) into the including class.

 4
Author: Ho-Sheng Hsiao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-22 21:02:06

Lo aprendí antes, pero lo aprecio cuando lo uso. Aquí está la diferencia:

Esto no funciona, pero funcionaría si lo he definido como def page_views(campaign):

class UserAction
  include Calculations

  def self.page_views(campaign)
    overall_profit =  calculate_campaign_profit(campaign)
  end
end

Esto funciona:

class UserAction
  extend Calculations

  def self.page_views(campaign)
    overall_profit =  calculate_campaign_profit(campaign)
  end
end
 1
Author: Caner Çakmak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-02 12:21:13

También me gustaría explicar el mecanismo como funciona. Si no tengo razón, por favor corrija.

Cuando usamos include estamos 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 sí. Así que cuando a recibe mesage some_method comienza el método de búsqueda some_method en la clase propia de a, luego en la clase A y luego en los módulos de clase vinculados a A si hay algunos (en orden inverso, último incluido ganar).

Cuando usamos extend estamos agregando enlace a un módulo en la clase eigen del objeto. Así que si usamos A. nuevo.extend (MyMod) estamos agregando un enlace a nuestro módulo a la clase eigen de instancia de A o a la clase a'. Y si usamos A. extend (MyMod) estamos agregando un enlace a A(object's, las clases también son objetos) eigenclass A'.

Así que la ruta de búsqueda del método para a es la siguiente: a = > a' = > módulos vinculados a a ' class = > A.

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

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

Lo siento por mi mal inglés.

 1
Author: user1136228,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-27 12:11:02