Уточнения
Благодаря открытым классам 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.