В чем разница между включением и расширением в Ruby?
Вопрос
Просто разбираюсь в метапрограммировании Ruby.Миксины/модули всегда меня сбивают с толку.
- включать:смешивается с указанными методами модуля как методы экземпляра в целевом классе
- продлевать:смешивается с указанными методами модуля как методы класса в целевом классе
Так в этом ли главная разница или здесь скрывается более крупный дракон?например
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!"
Решение
То, что вы сказали, верно.Однако это еще не все.
Если у вас есть класс Klazz
и модуль Mod
, включая Mod
в Klazz
приводит примеры Klazz
доступ к Mod
методы.Или вы можете продлить Klazz
с Mod
давая сорт Klazz
доступ к Mod
методы.Но вы также можете расширить произвольный объект с помощью o.extend Mod
.В этом случае отдельный объект получает Mod
методы, даже если все остальные объекты того же класса, что и o
не.
Другие советы
продлевать — добавляет методы и константы указанного модуля в метакласс цели (т.е.класс синглтона) например
- если ты позвонишь
Klazz.extend(Mod)
, теперь в Klazz есть методы Модов (как методы класса) - если ты позвонишь
obj.extend(Mod)
, теперь у obj есть методы Mod (как методы экземпляра), но нет другого экземпляра ofobj.class
добавлены эти методы. extend
это общедоступный метод
включать - По умолчанию методы указанного модуля смешиваются с методами экземпляра в целевом модуле/классе.например
- если ты позвонишь
class Klazz; include Mod; end;
, теперь все экземпляры Klazz имеют доступ к методам мода (как к методам экземпляра) include
— это частный метод, поскольку он предназначен для вызова из класса/модуля контейнера.
Однако, модули очень часто переопределить include
поведение обезьяны, исправляя included
метод.Это очень заметно в устаревшем коде Rails. подробнее от Йехуды Каца.
Более подробная информация о include
, с поведением по умолчанию, при условии, что вы запустили следующий код
class Klazz
include Mod
end
- Если Mod уже включен в Klazz или в один из его предков, оператор include не имеет никакого эффекта.
- Он также включает константы Модов в Klazz, если они не конфликтуют.
- Это дает Klazz доступ к переменным модуля Mod, например.
@@foo
или@@bar
- вызывает ArgumentError, если есть циклические включения
- Присоединяет модуль как непосредственного предка вызывающего объекта (т.е.Он добавляет мод в Klazz.ancestors, но мод не добавляется в цепочку Klazz.superclass.superclass.superclass.Итак, вызов
super
в Klazz#foo проверит Mod#foo перед проверкой метода foo настоящего суперкласса Klazz.Подробности смотрите в RubySpec.).
Конечно, документация ядра Ruby всегда лучшее место для этих вещей. Проект РубиСпец также был фантастическим ресурсом, потому что они точно документировали функциональность.
Правильно.
За кулисами include на самом деле является псевдонимом для append_features, который (из документации):
Реализация Ruby по умолчанию состоит в том, чтобы добавить константы, методы и переменные модуля этого модуля в амодуль, если этот модуль еще не был добавлен в амодуль или один из его предков.
Все остальные ответы хороши, включая совет покопаться в 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 внутри концерна получает расширенный (скопировано) во включающий класс.
Я также хотел бы объяснить механизм, как он работает.Если я не прав, пожалуйста, поправьте.
Когда мы используем include
мы добавляем связь из нашего класса с модулем, который содержит некоторые методы.
class A
include MyMOd
end
a = A.new
a.some_method
У объектов нет методов, они есть только у классов и модулей.Так когда a
получает сообщение some_method
это начать метод поиска some_method
в a
собственный класс, то в A
класс, а затем связан с A
модули классов, если они есть (в обратном порядке, побеждает последний включенный).
Когда мы используем extend
мы добавляем связь с модулем в собственном классе объекта.Поэтому, если мы используем A.new.extend(MyMod), мы добавляем связь нашего модуля с собственным классом экземпляра A или a'
сорт.И если мы используем A.extend(MyMod), мы добавляем связь с собственным классом A (объекты, классы также являются объектами). A'
.
поэтому путь поиска метода для a
как следует:a => a' => связанные модули с классом a' => A.
также есть метод prepend, который меняет путь поиска:
a => a' => добавленные модули к A => A => включенный модуль в A
Извините за мой плохой английский.