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

415

Просто разбираюсь в метапрограммировании 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!"
Gishu
источник
Проверьте эту ссылку тоже: juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby
Донато

Ответы:

249

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

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

domgblackwell
источник
324

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

  • если вы звоните 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 или одного из его предков, оператор include не действует
  • Он также включает константы мода в Klazz, если они не конфликтуют
  • Это дает Klazz доступ к переменным модуля Mod, например, @@fooили@@bar
  • вызывает ArgumentError, если есть циклические включения
  • Присоединяет модуль в качестве непосредственного предка вызывающего (т. Е. Добавляет мод к Klazz.ancestors, но мод не добавляется в цепочку Klazz.superclass.superclass.superclass. Таким образом, вызов superKlazz # foo проверит наличие Mod # foo перед проверкой к методу foo реального суперкласса Klazz. Подробности смотрите в RubySpec.)

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

Джон Даутат
источник
22
Я знаю, что это довольно старый пост, но ясность ответа не могла удержать меня от комментариев. Большое спасибо за хорошее объяснение.
МохамедСанаулла
2
@anwar Очевидно, но теперь я могу комментировать, и мне снова удалось найти статью. Это доступно здесь: aaronlasseigne.com/2012/01/17/explaining-include-and-extend, и я все еще думаю, что схема значительно облегчает понимание
systho
1
Большая победа в этом ответе заключается в том, как extendприменять методы как методы класса или экземпляра, в зависимости от использования. Klass.extend= методы класса, objekt.extend= методы экземпляра. Я всегда (ошибочно) предполагал, что методы класса происходят из extendэкземпляра include.
Фрэнк Коэль
16

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

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

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

Тоби хеде
источник
5

Когда вы 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модуль, его методы становятся методами класса .

Chintan
источник
3

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

Хо-Шэн Сяо
источник
1

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

Когда мы используем, 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' class => A.

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

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

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

user1136228
источник