Ruby mixins: расширять и включать - программирование
Подтвердить что ты не робот

Ruby mixins: расширять и включать

Я читал несколько статей о методах Ruby mixin, extend и include, и я все еще не совсем уверен в поведении. Я понимаю, что extend добавит методы экземпляра данного модуля как однотонные методы к модулю, выполняющему расширение, и что include по существу добавит содержимое модуля (методы, константы, переменные) к тому, которое делает в том числе, эффективно определяя их в приемнике.

Однако, после некоторого возиться, пытаясь понять, как будет проявляться поведение, у меня есть несколько вопросов. Вот моя настройка тестирования:

module Baz
  def blorg
    puts 'blorg'
  end
end

module Bar
  include Baz
  def blah
    puts 'blah'
  end
end

module Foo
  extend Bar
end

class Bacon
  extend Bar
end

class Egg
  include Bar
end

Как и следовало ожидать, модуль Bar получает методы экземпляра, определенные в Baz (#blorg), как если бы они были определены сами по себе из-за метода включения, а класс Bacon методы Bacon::blah и Bacon::blorg по расширению.

Bacon.blah  # => blah
Bacon.blorg # => blorg

И класс Egg получает методы, определенные в Bar (#blah и теперь #blorg) как методы экземпляра.

Egg.new.blah  # => blah
Egg.new.blorg # => blorg

Я получаю все это, так что хорошо.

Однако я не понимаю ответов, получаемых от методов #ancestors и #is_a?.

Bacon.ancestors  # => [Bacon, Object, Kernel, BasicObject]
Bacon.is_a? Bar  # => true

Egg.ancestors    # => [Egg, Bar, Baz, Object, Kernel, BasicObject]
Egg.is_a? Bar    # => false

Казалось бы, расширение модуля заставляет метод #is_a? возвращать true при запросе об этом модуле, но он не добавляется к предкам класса, и наоборот в отношении включения: предки класс содержит включенные модули, но метод #is_a? возвращает false при запросе. Почему это происходит?

4b9b3361

Ответ 1

Разница в том, что include добавит включенный класс к предкам класса включения, тогда как extend добавит расширенный класс к предкам класса Singleton класса расширяющихся классов. Уф. Пусть сначала наблюдаем, что происходит:

Bacon.ancestors
#=> [Bacon, Object, Kernel, BasicObject]

Bacon.singleton_class.ancestors
#=> [Bar, Baz, Class, Module, Object, Kernel, BasicObject]

Bacon.new.singleton_class.ancestors
#=> [Bacon, Object, Kernel, BasicObject]

Bacon.is_a? Bar
#=> true

Bacon.new.is_a? Bar
#=> false

И для класса Egg

Egg.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]

Egg.singleton_class.ancestors
#=> [Class, Module, Object, Kernel, BasicObject]

Egg.new.singleton_class.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]

Egg.is_a? Bar
#=> false

Egg.new.is_a? Bar
#=> true

Итак, что действительно делает foo.is_a? Klass, это проверить, содержит ли foo.singleton_class.ancestors Klass. Другое дело, что все предки класса становятся предками одноуровневого класса экземпляров при создании экземпляра. Таким образом, это будет оцениваться как true для всех вновь созданных экземпляров любого класса:

Egg.ancestors == Egg.new.singleton_class.ancestors

Итак, что все это значит? extend и include делают то же самое на разных уровнях, я надеюсь, что следующий пример делает это понятным, поскольку оба способа расширения класса по существу эквивалентны:

module A
  def foobar
    puts 'foobar'
  end
end

class B
  extend A
end

class C
  class << self
    include A
  end
end

B.singleton_class.ancestors == C.singleton_class.ancestors
#=> true

где class << self - это просто нечетный синтаксис, чтобы перейти к одиночному классу. Таким образом, extend действительно является сокращением для include в одноэлементном классе.

Ответ 2

Egg.is_a? Egg # => false

Включает (эффективно) меняет экземпляры класса Egg. Хотя это не совсем то же самое, он очень похож на что-то вроде

class Egg < Bar
end

Когда расширение добавит методы класса, так что это очень похоже на выполнение чего-то вроде

class Bacon
  class << self
    include Bar
  end
end

Вы можете думать об этом, например, включать экземпляры изменений класса, где в качестве расширения фактически изменяется класс.