В чем разница между включением и расширением в Ruby?

Просто оглядываясь вокруг метапрограммирования Руби. Mixin/modules всегда меня путают.

  • включить: смешивает в указанных модульных методах как методы экземпляра в целевом классе
  • expand: смешивает в указанных модульных методах как методы класса в целевом классе

Итак, главное отличие только этого или более крупного дракона скрывается? например.

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!"

Ответ 1

То, что вы сказали, верно. Однако для этого есть нечто большее.

Если у вас есть класс Klazz и module Mod, в том числе Mod в Klazz, он предоставляет экземпляры Klazz доступа к методам Mod. Или вы можете расширить Klazz с помощью Mod, предоставляя классу Klazz доступ к методам Mod. Но также вы можете расширить произвольный объект с помощью o.extend Mod. В этом случае отдельный объект получает методы Mod, хотя все остальные объекты с тем же классом, что и o, не делают.

Ответ 2

expand - добавляет указанные методы и константы модуля к целевому метаклассу (т.е. одноэлементному классу) например

  • если вы вызываете Klazz.extend(Mod), теперь Klazz имеет методы Mod (как методы класса)
  • если вы вызываете obj.extend(Mod), теперь obj имеет методы Mod (как методы экземпляра), но ни один другой экземпляр obj.class не добавил эти методы.
  • extend является общедоступным методом.

включить. По умолчанию он смешивается в указанных модульных методах как методы экземпляра в целевом модуле/классе. например.

  • если вы вызываете class Klazz; include Mod; end;, теперь все экземпляры Klazz имеют доступ к методам Mod (в качестве методов экземпляра).
  • include - частный метод, поскольку он предназначен для вызова из класса/модуля контейнера.

Однако, модули очень часто переопределяют поведение include путем обезболивания метода included. Это очень заметно в устаревшем коде Rails. подробная информация от Yehuda Katz.

Дополнительная информация о include с поведением по умолчанию при условии, что вы выполнили следующий код

class Klazz
  include Mod
end
  • Если Mod уже включен в Klazz или один из его предков, оператор include не имеет эффекта
  • Он также включает константы Mod в Klazz, если они не сталкиваются
  • Он предоставляет Klazz доступ к переменным модуля Mod, например. @@foo или @@bar
  • вызывает ArgumentError, если есть циклические включения
  • Прилагает модуль как непосредственный предок вызывающего (т.е. добавляет Mod к Klazz.ancestors, но Mod не добавляется в цепочку Klazz.superclass.superclass.superclass. Поэтому вызов super в Klazz # foo будет проверять для Mod # foo, прежде чем проверять метод суперкласса Klazz реального класса. Подробнее см. в RubySpec.).

Конечно, документация рубинового ядра всегда является лучшим местом для этих вещей. Проект RubySpec также был фантастическим ресурсом, поскольку они точно фиксировали функциональность.

Ответ 3

Это правильно.

За кулисами include на самом деле является псевдонимом для append_features, который (из документов):

Реализация по умолчанию Ruby добавить константы, методы и модуль переменные этого модуля в aModule if этот модуль еще не добавлен к aModule или одному из его предков.

Ответ 4

Все остальные ответы хороши, включая подсказку, чтобы вырыть 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

Что касается вариантов использования:

Если вы включите модуль ReusableModule в класс ClassThatIncludes, ссылки на методы, константы, классы, подмодули и другие объявления.

Если вы расширяете класс ClassThatExtends с помощью модуля ReusableModule, то методы и константы копируются. Очевидно, что если вы не будете осторожны, вы можете потратить много памяти, динамически дублируя определения.

Если вы используете ActiveSupport:: Concern, функция .included() позволяет напрямую переписать включенный класс. модуль ClassMethods внутри Концерна расширяется (копируется) в класс включения.

Ответ 5

Я узнал это раньше, но ценю его, когда я его использую. Вот разница:

Это не работает, но будет работать, если я определил его как def page_views(campaign):

class UserAction
  include Calculations

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

Это работает:

class UserAction
  extend Calculations

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

Ответ 6

Я также хотел бы объяснить механизм, как он работает. Если я не прав, исправьте.

Когда мы используем include, мы добавляем ссылку из нашего класса в модуль, который содержит некоторые методы.

class A
include MyMOd
end

a = A.new
a.some_method

Объекты не имеют методов, только кланы и модули. Поэтому, когда a получает mesage some_method, он начинает метод поиска some_method в a собственном классе, затем в классе a, а затем связан с модулями класса a, если есть некоторые (в обратном порядке, последний включая победы).

Когда мы используем extend, мы добавляем связь с модулем в собственном классе объекта. Поэтому, если мы используем A.new.extend(MyMod), мы добавляем привязку к нашему модулю к собственному классу экземпляра или классу a'. И если мы используем A.extend(MyMod), мы добавляем связь с A (объекты, классы также являются объектами) eigenclass a'.

поэтому путь поиска метода для a выглядит следующим образом: a = > a '= > связанным модулям с классом' class= > A.

также существует метод prepend, который изменяет путь поиска:

a = > a '= > добавленный модуль A = > A = > включен в A

Извините за мой плохой английский.

Ответ 7

Когда вы include модуль в класс, методы модуля импортируются как методы экземпляра.

Однако когда вы extend модуль в класс, методы модуля импортируются как методы класса.

Например, если у нас есть модуль Module_test, определенный следующим образом:

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

Теперь для include модуля. Если мы определим класс A следующим образом:

class A
  include Module_test
end

a = A.new
a.func

Вывод будет: M - in module.

Если мы заменим строку include Module_test на extend Module_test и снова запустим код, мы получим следующую ошибку: undefined method 'func' for #<A:instance_num> (NoMethodError).

Изменив вызов метода a.func на A.func, вывод изменится на: M - in module.

Из приведенного выше выполнения кода ясно, что когда мы include модуль, его методы становятся методами экземпляра, а когда мы extend модуль, его методы становятся методами класса.