Ответ 1
Я попытаюсь объяснить это немного глубже:
когда вы include
модуль в какой-то класс, Ruby создает специальный внутренний класс include и добавляет его в иерархию (обратите внимание, что в основном вам не разрешено включать класс include из Ruby-программы, это скрытый класс):
Given A inherits B
And we have a module C
When A includes C
Then A inherits includeC inherits B
Если у включенного модуля есть другие включенные Модули, то includeModules также будут созданы для этих модулей:
Given A inherits B
And we have a module C
And C includes module D
When A includes C
Then A inherits includeC inherits includeD inherits B
Включить таблицу методов класса C - это ссылка на таблицу методов исходного класса C.
Когда вы extend
некоторый объект с модулем, то этот модуль включен в одноэлементный класс этого объекта, таким образом:
class << self; include C; end
# is the same as
extend C
Переход к вашему примеру:
module Kernel
extend Talk
end
Здесь вы включаете модуль Talk
в одноэлементный класс Kernel
(Kernel
является объектом класса Module
). Вот почему вы можете вызвать метод hello
только для объекта Kernel: Kernel.hello
.
Если мы напишем это:
module Kernel
include Talk
end
Затем Kernel
будет внутренне наследовать include include includeTalk (класс со ссылкой на методы Talk
).
Но модуль ядра уже включен в Object
- Object
наследует свой собственный класс includeKernel, а includeKernel-класс имеет ссылку на таблицу методов Kernel
и не видит методы новых классов include Kernel
.
Но теперь, если вы повторно включите Kernel в Object, все объекты будут видеть методы Talk:
> module Talk
> def hi
> puts 'hi'
> end
> end
=> nil
> module Kernel
> include Talk
> end
=> Kernel
> hi
NameError: undefined local variable or method `hi` for main:Object
from (irb):9
from /usr/share/ruby-rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>`
> class Object
> include Kernel
> end
=> Object
> hi
hi
=> nil
Решение для вас probjem может состоять в том, чтобы расширить основной объект с помощью вашего нового модуля:
extend Talk
Надеюсь, что это объясняется поведением, которое вы наблюдаете:)
UPDATE
Попробуем уточнить ваши вопросы:
Я все еще немного смущен, почему мне нужно повторно включить Kernel в Object. В случаи, которые не связаны с основным объектом, я могу создать экземпляр объекта основанный на классе, а затем снова откроет этот класс и включит модуль, и этот объект увидит методы в моем модуле. Здесь что-то другое в том, как основной объект включает в себя ядро? я также не уверен, что вы подразумеваете под "Object наследует свой собственный includeKernel class и includeKernel class..." Почему он не видит новые включенные модуль в ядре?
Расскажите о случае с прямым включением модуля в класс объекта:
module M
def hi
puts 'hi'
end
end
class C
end
c = C.new
c.hi # => UndefinedMethod
class C
include M
end
c.hi # => hi
в этом случае у вас будет объект c
класса c
. Класс c
наследует Object
(потому что это экземпляр класса Class
. C ищет свои методы экземпляра в своем одноэлементном классе → тогда в своем классе C → , то в родителях класса C (в этом случае Object
методы экземпляра). Когда мы включим модуль M
в класс c
, то includeM
будет суперклассом c
, а если c
не найдет свой метод экземпляра в своем одноэлементном классе и c
класс, он будет искать методы экземпляра в includeM
. includeM
имеет ссылку на таблицу методов класса M
(экземпляр класса Module
). Таким образом, когда c
ищет метод экземпляра hi
, он находит это в модуле M
.
Но это отличается от случая, когда вы включаете модуль M
в модуль Kernel
.
В начале программы Object
класс включает модуль Kernel
: class Object; include Kernel; end
. Вот почему я говорю, что Object
наследует от includeKernel
. includeKernel
имеет ссылку на таблицу методов Kernel
, и когда вы меняете таблицу методов ядра, includeKernel
также видит эти изменения:
module Kernel
def hi # add hi method to method table of Kernel
puts 'hi'
end
end
hi # => hi # any Object now see method hi
Но когда вы включаете модуль M в Kernel, таблица методов ядра не изменяется. Вместо этого ядро теперь наследует includeM
include class. includeKernel
не видят методы includeM
, потому что он не знает о цепочке наследования Kernel
и includeM
, он знает только таблицу методов Kernel
.
Но когда вы снова включите Kernel
в Object
, механизм включения увидит, что Kernel
включает M
и создаст includeM для Object
. Теперь Object
наследует includeKernel
наследует includeM
наследует BasicObject
.