В чем разница между включением и расширением в 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
модуль, его методы становятся методами класса.