精炼¶ ↑
由于 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
)的更改或精炼。精炼块中的 self
是此匿名模块,类似于 Module#module_eval
。
使用 using 激活精炼
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
在定义 MUser#call_foo
的 m_user.rb
中处于活动状态,因此当 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
请注意,如果稍后重新打开类 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
中,因此方法查找不会向上移动到超类。
但是,如果在精炼中的 Numeric
上定义了方法 foo
,则 1.foo
会调用该方法,因为 foo
不存在于 Integer
上。
super
¶ ↑
当调用 super
时,方法查找会检查
-
当前类的包含模块。请注意,当前类可能是一个精炼。
-
如果当前类是精炼,则方法查找将按照上面的
Method
查找部分中的方式进行。 -
如果当前类具有直接超类,则方法查找将按照上面的
Method
查找部分中的方式使用超类进行。
请注意,即使在同一上下文中激活了另一个精炼,精炼的方法中的 super
也会调用精炼类中的方法。这仅适用于精炼的方法中的 super
,而不适用于精炼中包含的模块中的方法中的 super
。
方法内省¶ ↑
当使用 Kernel#method 或 Kernel#methods 等内省方法时,不会考虑精炼。
此行为将来可能会更改。
通过 Module#include
的 Refinement
继承¶ ↑
当将模块 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。该规范还包含更多详细信息。