Уточнения
Благодаря открытым классам 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 соответственно.
Уточнения обладают лексическим объёмом. Когда управление передаётся за пределы области действия, уточнение деактивируется. Это означает, что если вы требуете или загружаете файл или вызываете метод, определённый вне текущей области действия, уточнение будет деактивировано:
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
В 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}]"
Вы также можете активировать уточнения в определении класса или модуля, в этом случае уточнения активируются с момента вызова использования до конца определения класса или модуля:
# not activated here
class Foo
# not activated here
using M
# activated here
def foo
# activated here
end
# activated here
end
# not activated here
Обратите внимание, что уточнения в M не активируются автоматически, даже если класс Foo будет повторно открыт позже.
Поиск метода
При поиске метода для экземпляра класса 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.