Уточнения
Благодаря открытым классам 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, уточнения не учитываются.
Это поведение может быть изменено в будущем.
Наследование уточнений модулем 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.
Уточнения в потомках имеют более высокий приоритет, чем уточнения предков.
Дополнительное чтение
См. bugs.ruby-lang.org/projects/ruby-master/wiki/RefinementsSpec для текущей спецификации реализации уточнений. Спецификация также содержит более подробные сведения.
Ruby Core © 1993–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.