Spec-Zone.ru › Ruby 2.3

Уточнения

Благодаря открытым классам Ruby, вы можете переопределять или добавлять функциональность к существующим классам. Это называется «заплатой обезьяны». К сожалению, область действия таких изменений глобальна. Все пользователи класса, подвергшегося «заплате обезьяны», видят те же изменения. Это может привести к непредвиденным побочным эффектам или сбоям программ.

Уточнения разработаны для уменьшения влияния «заплат обезьяны» на других пользователей затронутого класса. Уточнения предоставляют способ локального расширения класса.

Вот базовое уточнение:

class C
  def foo
    puts "C#foo"
  end
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

Сначала определяется класс C. Затем создаётся уточнение для C с помощью Module#refine. Уточнения изменяют только классы, а не модули, поэтому аргумент должен быть классом.

Module#refine создаёт анонимный модуль, содержащий изменения или уточнения для класса (C в примере). self в блоке refine — это этот анонимный модуль, аналогичный Module#module_eval.

Активировать уточнение можно с помощью:

using M

c = C.new

c.foo # prints "C#foo in M"

Область действия

Вы можете активировать уточнения на верхнем уровне, а также внутри классов и модулей. Активировать уточнения внутри методов нельзя. Уточнения активируются до конца текущего определения класса или модуля или до конца текущего файла, если они используются на верхнем уровне.

Вы можете активировать уточнения в строке, переданной в Kernel#eval. Уточнения активны до конца строки eval.

Уточнения имеют лексическую область действия. Уточнения активны только в области действия после вызова using. Любой код перед оператором using не будет иметь активного уточнения.

Когда управление передаётся за пределы области действия, уточнение деактивируется. Это означает, что если вы загружаете или подключаете файл или вызываете метод, определённый за пределами текущей области действия, уточнение будет деактивировано:

class C
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

def call_foo(x)
  x.foo
end

using M

x = C.new
x.foo       # prints "C#foo in M"
call_foo(x) #=> raises NoMethodError

Если метод определён в области действия, где активен уточнение, уточнение будет активным при вызове метода. Этот пример охватывает несколько файлов:

c.rb:

class C
end

m.rb:

require "c"

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

m_user.rb:

require "m"

using M

class MUser
  def call_foo(x)
    x.foo
  end
end

main.rb:

require "m_user"

x = C.new
m_user = MUser.new
m_user.call_foo(x) # prints "C#foo in M"
x.foo              #=> raises NoMethodError

Поскольку уточнение M активно в m_user.rb, где MUser#call_foo определён, оно также активно, когда main.rb вызывает call_foo.

Поскольку using — это метод, уточнения активны только при вызове. Вот примеры, где уточнение M активно и неактивно.

В файле:

# not activated here
using M
# activated here
class Foo
  # activated here
  def foo
    # activated here
  end
  # activated here
end
# activated here

В классе:

# not activated here
class Foo
  # not activated here
  def foo
    # not activated here
  end
  using M
  # activated here
  def bar
    # activated here
  end
  # activated here
end
# not activated here

Обратите внимание, что уточнения в M не активируются автоматически, если класс Foo будет повторно открыт позже.

В eval:

# not activated here
eval <<EOF
  # not activated here
  using M
  # activated here
EOF
# not activated here

Когда не оценивается:

# not activated here
if false
  using M
end
# not activated here

При определении нескольких уточнений в одном модуле, внутри блока refine все уточнения из того же модуля активны при вызове уточнённого метода:

module ToJSON
  refine Integer do
    def to_json
      to_s
    end
  end

  refine Array do
    def to_json
      "[" + map { |i| i.to_json }.join(",") + "]"
    end
  end

  refine Hash do
    def to_json
      "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}"
    end
  end
end

using ToJSON

p [{1=>2}, {3=>4}].to_json # prints "[{\"1\":2},{\"3\":4}]"

Поиск Метода

При поиске метода для экземпляра класса C Ruby проверяет:

  • Если для C активны уточнения, в обратном порядке их активации:

    • Предваряемые модули из уточнения для C

    • Уточнение для C

    • Включённые модули из уточнения для C

  • Предваряемые модули C

  • C

  • Включённые модули C

Если метод не был найден ни на одном шаге, это повторяется с родительским классом C.

Обратите внимание, что методы в подклассе имеют приоритет над уточнениями в родительском классе. Например, если метод / определён в уточнении для Integer 1 / 2, вызывается исходный Fixnum#/, поскольку Fixnum — подкласс Integer и проверяется до уточнений для родительского класса Integer.

Если метод foo определён для Integer в уточнении, 1.foo вызывает этот метод, так как foo не существует в Fixnum.

super

При вызове super поиск метода проверяет:

  • Включённые модули текущего класса. Обратите внимание, что текущий класс может быть уточнением.

  • Если текущий класс является уточнением, поиск метода продолжается, как в разделе «Поиск Метода» выше.

  • Если у текущего класса есть непосредственный родительский класс, поиск метода продолжается, как в разделе «Поиск Метода» выше, используя родительский класс.

Обратите внимание, что super в методе уточнения вызывает метод в уточнённом классе, даже если существует другое уточнение, активированное в том же контексте.

Непосредственные вызовы методов

При использовании косвенного доступа к методам, например, Kernel#send, Kernel#method или Kernel#respond_to?, уточнения не учитываются для контекста вызывающей стороны при поиске метода.

Это поведение может быть изменено в будущем.

Дополнительные материалы

См. bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec для текущего описания реализации уточнений. В описании также содержатся более подробные сведения.

Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API