Вникаем в include и extend

в 18:14, , рубрики: extend, extended, included, ruby

Вникаем в include и extend
Все рубисты знакомы с формальными определениями для include и extend. Вы делаете include модуля, чтобы добавить методы экземпляра класса, и extend — чтобы добавить методы класса. К сожалению, данные определения не совсем точны. Они не могут объяснить почему мы используем instance.extend(Module), чтобы добавить методы объекту. Разве не должны мы в этом случае использовать instance.include(Module)? Чтобы разобраться в этом вопросе, начнем с выяснения где же хранятся методы.

Методы, которые я храню для тебя и парень, которые хранит мои методы

Объекты в Ruby не хранят свои собственные методы. Вместо этого они создают синглтон-класс, чтобы он хранил их методы.

class A
  def self.who_am_i
    puts self
  end

  def speak_up(input)
    puts input.upcase
  end
end

Интерпретатор создаст класс A и прикрепленный к нему синглтон-класс (мы будем ссылаться на синглтон-класс объекта используя префикс перед именем объекта). Любые методы экземпляра класса (как speak_up) добавляются к методам, хранящимся в классе A. Методы класса (как who_am_i) хранятся в классе ‘A.

A.singleton_methods # методы в классе ‘A
#=> [:who_am_i]

Вникаем в include и extend

Тоже самое происходит с экземплярами класса. Если у нас есть объект класса A и мы добавляем к нему метод, мы не можем хранить этот метод внутри самого объекта. Запомните — объекты в Ruby не хранят свои собственные методы.

a = A.new

def a.not_so_loud(input)
  puts input.downcase
end

Здесь опять создается синглтон-класс для объекта "a", чтобы он хранил метод not_so_loud.

Вникаем в include и extend

Теперь у нас есть метод, который принадлежит только объекту "a" и не затрагивает другие объекты класса A.

Я — свой отец?

Класс A содержит методы и информацию о цепи наследования, нужные для объекта "a" и всех остальных объектов класса A. Точно также синглтон-класс ‘A содержит методы и информацию о цепи наследования для класса A. Вы можете считать класс A объектом класса ‘A. Уловка в том, что мы не можем напрямую обращаться к синглтон-классу ‘A. Это значит, что нам нужно как-то различать добавление методов в A и в ‘A. Тут то include и extend и вступают в игру.

include

Когда вы делаете include модуля в объект, вы добавляете методы в цепь наследования объекта.

class A
  include M
end

Вникаем в include и extend

В этом легко убедиться, проверив предков класса A.

A.ancestors
#=> [A, M, Object, Kernel, BasicObject]

extend

extend — это тоже самое, что сделать include, но для синглтон-класса объекта.

class A
  extend M
end

Вникаем в include и extend

И снова мы можем подтвердить это, проверив предков класса ‘A.

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

Также мы можем использовать extend и для объекта.

a = A.new

a.extend(M)
a.singleton_class.ancestors
#=> [M, A, Object, Kernel, BasicObject]

Вникаем в include и extend

Если вы думаете об extend как о способе просто добавить методы класса, то все что мы сейчас сделали не имеет особого смысла. Однако, если вы посмотрите на это как на способ добавить методы к синглтон-классу объекта, то приведенные выше примеры станут яснее.

Хук included

Каждый вызов include проверяет подключаемый модуль на наличие метода included. Этот метод исполняется, когда модуль подключается, используя include. Это как конструктор (initialize) для подключений. Как вы возможно догадались, у extend для этих целей есть собственный метод — extended. Поэтому когда вы хотите добавить сразу и методы класса и методы экземпляра класса, вы можете использовать для этого хук included.

module M
  def self.included(base)
    base.extend(ClassMethods)
  end

  def speak_up(input)
    puts input.upcase
  end

  module ClassMethods
    def who_am_i
      puts self
    end
  end
end

class C
  include M
end

c = C.new

Сначала мы включаем модуль M в цепь наследования класса C.

Вникаем в include и extend

Затем мы расширяем класс C, добавляя методы в цепь наследования класса ‘C.

Вникаем в include и extend

Заключение

Когда начинаешь копать глубже типичного использования include и extend, то обнаруживаешь нечто странное и пугающее. Однако стоит понять реализацию лежащую в основе и все сразу обретает смысл. Давайте теперь дадим определение include и extend еще раз.

include — добавляет методы модуля объекту.
extend — вызывает include для синглтон-класса объекта.

Если вас интересует еще больше деталей о том, как работает интерпретатор, то я рекомендую к просмотру выступление Patrick Farley на Ruby Internals.

Примечание переводчика: еще один момент почему мы не можем использовать instance.include(Module) — это то, что метод include является приватным методом класса Module. В целом же до прочтения этой статьи я тоже не так представлял себе работу extend и include, поэтому посчитал стоящим ее перевести.

Автор: Svyatov


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js