Spec-Zone.ru › Ruby 3.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.

Поскольку использование — это метод, усовершенствования активны только при его вызове. Вот примеры того, где усовершенствование 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 блоков, все усовершенствования из одного модуля активны, когда вызывается отработанный метод (любой из методов to_json из примера ниже):

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}]"

Method Поиск метода

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

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

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

    • Усовершенствование для C

    • Включённые модули из усовершенствования для C

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

  • C

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

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

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

Однако, если метод foo определён на Numeric в усовершенствовании, 1.foo вызывает этот метод, поскольку foo не существует в Integer.

super

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

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

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

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

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

Интроспекция методов

При использовании методов интроспекции, таких как Kernel#method или Kernel#methods, усовершенствования не учитываются.

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

Refinement наследование у Module#include

Когда модуль X включается в модуль Y, Y наследует усовершенствования от X.

Например, C наследует усовершенствования от A и B в следующем коде:

module A
  refine X do ... end
  refine Y do ... end
end
module B
  refine Z do ... end
end
module C
  include A
  include B
end

using C
# Refinements in A and B are activated here.

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

Дополнительная информация

См. github.com/ruby/ruby/wiki/Refinements-Spec для текущего описания реализации усовершенствований. В описании также содержится более подробная информация.

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

Spec-Zone.ru

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