Каква е разликата между включване и разширяване в 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!"

person Gishu    schedule 01.10.2008    source източник
comment
Вижте и тази връзка: juixe.com/techknow /index.php/2006/06/15/mixins-in-ruby   -  person Donato    schedule 20.12.2016


Отговори (6)


Това, което казахте, е правилно. Има обаче нещо повече от това.

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

person domgblackwell    schedule 01.10.2008
comment
лаконичен като Конфуций. - person lkahtz; 23.07.2020

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

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

включване – По подразбиране той смесва методите на посочения модул като методи на екземпляр в целевия модул/клас. напр.

  • ако извикате class Klazz; include Mod; end;, сега всички екземпляри на Klazz имат достъп до методите на Mod (като методи на екземпляр)
  • include е частен метод, тъй като е предназначен да бъде извикан от класа/модула на контейнера.

Въпреки това модулите много често заменят поведението на include чрез маймунски корекции на метода included. Това е много видно в наследения код на Rails. повече подробности от Йехуда Кац.

Допълнителни подробности за include, с поведението му по подразбиране, ако приемем, че сте изпълнили следния код

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

Разбира се, документацията за ruby ​​core винаги е най-доброто място за тези неща. Проектът RubySpec също беше фантастичен ресурс, тъй като те документираха точно функционалността.

person John Douthat    schedule 15.02.2011
comment
Знам, че това е доста стара публикация, но яснотата на отговора не можа да ме спре да коментирам. Благодаря много за хубавото обяснение. - person MohamedSanaulla; 25.12.2011
comment

РЕДАКТИРАНЕ: Виждам, че бях дерайлиран и в крайна сметка отговорих на въпрос, различен от зададения. Отговорът на истинския въпрос е в дъното на отговора на Пол Томблин. (Ако искате да подобрите това решение, за да пренасочите stdout и stderr отделно по някаква причина, можете да използвате техниката, която описвам тук.)


Исках отговор, който запазва разликата между stdout и stderr. За съжаление всички отговори, дадени досега, които запазват това разграничение, са склонни към раса: те рискуват програмите да видят непълен вход, както посочих в коментарите.

Мисля, че най-накрая намерих отговор, който запазва разграничението, не е склонен към раса и също не е ужасно объркан.

Първи градивен блок: за размяна на stdout и stderr:

my_command 3>&1 1>&2 2>&3-

Втори градивен елемент: ако искахме да филтрираме (напр. tee) само stderr, бихме могли да постигнем това чрез размяна на stdout&stderr, филтриране и след това обратно размяна:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

Сега останалото е лесно: можем да добавим stdout филтър или в началото:

{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

или накрая:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter

За да се убедя, че и двете горни команди работят, използвах следното:

alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'

Изходът е:

...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr

и моята подкана се връща веднага след "teed stderr: to stderr", както се очаква.

Бележка под линия относно zsh:

Горното решение работи в bash (и може би в някои други обвивки, не съм сигурен), но не работи в zsh. Има две причини да се провали в zsh:

  1. синтаксисът 2>&3- не се разбира от zsh; това трябва да се пренапише като 2>&3 3>&-
  2. в zsh (за разлика от други обвивки), ако пренасочите файлов дескриптор, който вече е отворен, в някои случаи (не разбирам напълно как решава) вместо това прави вградено подобно на tee поведение. За да избегнете това, трябва да затворите всеки fd, преди да го пренасочите.

Така например второто ми решение трябва да бъде пренаписано за zsh като {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter (което работи и в bash, но е ужасно многословно).

От друга страна, можете да се възползвате от мистериозното вградено имплицитно teeing на zsh, за да получите много по-кратко решение за zsh, което изобщо не изпълнява tee:

my_command >&1 >stdout.txt 2>&2 2>stderr.txt

(Не бих предположил от документите, които открих, че >&1 и 2>&2 са нещото, което задейства имплицитното teeing на zsh; открих това чрез проба-грешка.)

- person systho; 09.03.2016
comment
Голямата победа в този отговор е как extend може да прилага методи като методи на клас или екземпляр, в зависимост от използването. Klass.extend = методи на клас, objekt.extend = методи на екземпляр. Винаги (погрешно) приемах, че методите на клас идват от extend, а екземплярът от include. - person Frank Koehl; 07.02.2018

Това е правилното.

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

Реализацията по подразбиране на Ruby е да добавя константите, методите и модулните променливи на този модул към aModule, ако този модул вече не е добавен към aModule или към някой от неговите предци.

person Toby Hede    schedule 01.10.2008

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

person Chintan    schedule 20.09.2019

Всички останали отговори са добри, включително съвета да се разровите в 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 вътре в Concern се разширява (копира) във включващия клас.

person Ho-Sheng Hsiao    schedule 22.09.2011

Бих искал също да обясня механизма, както работи. Ако не съм прав, моля поправете.

Когато използваме 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' клас => A.

също има метод за добавяне, който променя пътя за търсене:

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

съжалявам за лошия ми английски.

person user1136228    schedule 27.03.2016