细化¶ ↑
由于 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
。接下来,使用 Module#refine
为 C
创建一个细化。
Module#refine
创建一个匿名模块,其中包含对类(示例中的 C
)的更改或细化。refine
块中的 self
与 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
在 m_user.rb
中定义,因此在 main.rb
调用 call_foo
时,细化 M
也处于激活状态。
由于 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
请注意,如果稍后重新打开类 Foo
,则 M
中的细化不会自动激活。
在 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。该规范还包含更多详细信息。