Refinements
改进
由于 Ruby 的开放类,您可以重新定义或添加功能到现有的类。这被称为 “monkey patching”。不幸的是,这种变化的范围是全球性的。monkey patching 类的所有用户都会看到相同的更改。这可能会导致意外的副作用或程序中断。
细化旨在减少 monkey patching 对 monkey patching 类的其他用户的影响。精化提供了一种在本地扩展类的方法。
这是一个基本的改进:
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
在精炼块中是这个匿名模块,类似于 Module#module_eval。
使用以下命令激活优化:
using M
c = C.new
c.foo # prints "C#foo in M"
Scope
您可以在顶层和类和模块内激活细化。您可能无法激活方法范围中的细化。细化被激活直到当前类或模块定义结束,或者直到当前文件的结尾(如果在顶层使用)。
您可以在传递给 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
在课堂上:
# 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
请注意,如果稍后重新打开 Foo 类,则 M 中的优化不会自动激活。
在评估中:
# 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
当在同一个模块中定义多个细化时,在细化块内部,当调用细化方法时,来自同一模块的所有细化都将处于活动状态:
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
,按相反顺序激活:
- The prepended modules from the refinement for `C`
- The refinement for `C`
- The included modules from the refinement for `C`
- 前置模块
C
如果在任何时候没有发现任何方法,则重复使用超类C
。
请注意,子类中的方法优先于超类中的优化。例如,如果在/
Intege r的细化中定义该方法,则会1 / 2
调用原始的 Fixnum#/
,因为Fixnum是 Integer 的一个子类,并且在超类 Integer 的细化之前进行搜索。
如果方法foo
在细化中定义在 Integer 上,则1.foo
调用该方法,因为 Fixnum 上不存在foo
方法。
super
当super
被调用的方法查找检查时:
- 包含当前课程的模块。请注意,目前的课程可能是一个改进。
请注意,super
在细化的方法中,即使存在已在相同上下文中激活的另一个细化,也会调用细化类中的方法。
间接方法调用
当使用间接方法访问,如 Kernel#send,Kernel#method 或 Kernel#respond_to?在方法查找过程中,调用者上下文并不满足优化。
这种行为在未来可能会改变。