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

Solo me estoy metiendo en la metaprogtwigción de Ruby. Los mixin / modules siempre logran confundirme.

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

Entonces, ¿la mayor diferencia es esto o está acechando un dragón más grande? 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!" 

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

Si tiene una clase Klazz y módulo Mod , incluyendo Mod en Klazz da ejemplos de acceso de Klazz a los métodos de Mod . O puede extender Klazz con Mod dando a la clase Klazz acceso a los métodos de Mod . Pero también puedes 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.

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

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

include – Por defecto, 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 llamas a la class Klazz; include Mod; end; 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 a menudo anulan el comportamiento de include parcheando el método included . 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 antecesores, la sentencia include no tiene ningún efecto
  • También incluye las constantes de Mod en Klazz, siempre que no entren en conflicto
  • Le da a Klazz acceso a las variables del módulo de Mod, por ejemplo, @@foo o @@bar
  • plantea ArgumentError si hay inclusiones cíclicas
  • Adjunta el módulo como el antecesor 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. Por lo tanto, al llamar a super en Klazz # foo comprobará Mod # foo antes consultando el método foo de la verdadera superclase de Klazz. Consulte el 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, ya que documentaron la funcionalidad con precisión.

  • #include rubydoc RubySpec
  • #included rubydoc RubySpec
  • #extend RubySpec rubydoc
  • #extended rubySpec rubydoc
  • #extend_object RubySpec rubydoc
  • #append_features rubySpec rubydoc

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 alguno de sus antecesores.

Todas las demás respuestas son buenas, incluida la sugerencia de profundizar en 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 perder 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 Concern se extiende (copia) a la clase incluida.

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

Esto no funciona, pero funcionaría si lo hubiera 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 

También me gustaría explicar el mecanismo, ya que funciona. Si no estoy bien, corrígelo.

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 lo hacen. Entonces, cuando a recibe mesage some_method , comienza el método de búsqueda some_method en la clase propia de una, luego en A class y luego en los módulos de la clase A si hay algunos (en orden inverso, la última palabra incluida es wins).

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

por lo que la ruta de búsqueda de métodos para a es la siguiente: a => a ‘=> módulos vinculados a una’ clase => A.

también hay un método previo que cambia la ruta de búsqueda:

a => a ‘=> módulos añadidos a A => A => módulo incluido a A

Perdón por mi mal ingles.