В чем разница между включением и расширением в Ruby? - PullRequest
387 голосов
/ 01 октября 2008

Просто разбираюсь в метапрограммировании Ruby. Миксин / модули всегда меня смущают.

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

Значит, главное отличие в этом или скрывается больший дракон? например,

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

Ответы [ 5 ]

304 голосов
/ 15 февраля 2011

extend - добавляет методы и константы указанного модуля в метакласс целевого объекта (т. Е. Класс синглтона) например

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

include - по умолчанию он смешивает методы указанного модуля как методы экземпляра в целевом модуле / классе. например,

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

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

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

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

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

232 голосов
/ 01 октября 2008

То, что вы сказали, правильно. Однако это еще не все.

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

13 голосов
/ 01 октября 2008

Это верно.

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

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

3 голосов
/ 23 сентября 2011

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

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

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

1 голос
/ 27 марта 2016

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

Когда мы используем 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 (объекты, классы также являются объектами) eigenclass A'.

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

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

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

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

...