Ruby 2.0 的新特性 Refinements 介绍

很多人都知道在 Ruby 里面可以重新定义或者添加方法到已经存在的类里面, 这个叫做 “monkey patch"。但是这种定义是全局性的,所有使用这个类 的用户会被影响到,如果有其他gem定义了同样的方法名,可能还会引起混淆, 所以由此产生了 Refinements。

Refinements 就是用来减少 monkey patch 影响他人而产生的,他提供了一种 在本地继承类的方式。

Refinements 在 Ruby 2.0 里面还只是个实验,当然还是期望这个功能在之后的版本可以一直存在。 但有些行为会被改变,所以当你第一次使用的时候会有警告出现。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class C
  def foo
    puts "C#foo"
  end
end

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

refine 是 module 的方法,refine 只能改变 class ,是不能作用于 module 的。

如果想使用的话,使用 using 就可以了:

1
2
3
4
5
using M

x = C.new

c.foo # prints "C#foo in M"

作用范围 (scope)

你可以激活refinements在任何位置,不一定在类/模块/方法的里面。如果定义在Kernel#eval里面的话, 将会是全局性的。Refinement的作用范围是文件的结束或者eval string的结束位置。

如果这个refinements传入到当前的scope之外的话,将不会被激活。 例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

如果搞成多个文件的话,refinements只在引入的文件里面有效。

c.rb

1
2
class C
end

m.rb

1
2
3
4
5
6
7
8
9
require "c"

module M
  refine C do
    def foo
      puts "C#foo in M"
    end
  end
end

m_user.rb

1
2
3
4
5
6
7
8
9
require "m"

using M

class MUser
  def call_foo(x)
    x.foo
  end
end

main.rb

1
2
3
4
5
6
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_user.rb 里面使用了 using,所以 refinement 起作用了,call_foo就可以调用了。 下面举些例子说明 refinement 的作用范围,以便更好的理解。

在文件里面
1
2
3
4
5
6
7
8
9
10
11
# not activated here
using M
# activated here
class Foo
  # activated here
  def foo
    # activated here
  end
  # activated here
end
# activated here
在 eval 里面
1
2
3
4
5
6
7
# not activated here
eval <<EOF
# not activated here
using M
# activated here
EOF
# not activated here
在 if 判断条件里面
1
2
3
4
5
# not activated here
if false
  using M
end
# not activated here
定义多个refinements

如果同一个module定义了多个的话,所有的定义都会被激活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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}]"

你也可以定义在类或模块定义里面

他的作用范围将只是在类或模块定义结束为止。

1
2
3
4
5
6
7
8
9
10
11
# not activated here
class Foo
  # not activated here
  using M
  # activated here
  def foo
    # activated here
  end
  # activated here
end
# not activated here

评论