Ruby2.1中Refinements特性有哪些

这篇文章主要介绍“Ruby2.1中Refinements特性有哪些”,在日常操作中,相信很多人在Ruby2.1中Refinements特性有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Ruby2.1中Refinements特性有哪些”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

成都创新互联于2013年开始,先为灯塔等服务建站,灯塔等地企业,进行企业商务咨询服务。为灯塔企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

Monkey Patches(猴子补丁)一直是Ruby Open Class(开放类)特性的副作用, 不过也有人把Monkey Patches看作是Ruby的一种特性。为什么叫「猴子补丁」呢? 进化未完全呗,这也是Ruby社区中传出来的名字,不管如何,这个命名代表着贬义,意味着危险。

Ruby的开放类给开发者以很大的自由与灵活,但是,当你打开一个类,添加自己的方法的时候,你有没有想过这个方法会覆盖掉已有的方法,自己写的代码还好,但是你如果用Ruby内建的类,或者是第三方gem提供的类,Monkey Patches随时有可能让你的代码穿越。

有一个比较典型的案例,早年的Ruby1.8.7 preview版本之前,是没有Symbol#to_proc这个方法的,但是Rails自己通过Monkey Patch实现了Symbol#to_proc方法,结果Ruby1.8.7preview版本之后,添加了Symbol#to_proc方法,导致了Rails出现了一些不对劲的问题。

在Refinements出现之前,Ruby社区避免Monkey Patches的方法基本有以下几种:

  • 使用别名/ alias_method_chain

  • 使用module 加命名空间

  • 强制检查要添加的方法是否被定义

Ruby社区里讨论了好多年的Refinements特性,就是为了解决上述问题,让你安全的使用开放类。直到Ruby2.0才加进来这个特性,但是属于实验性的,不建议使用,但是前两天随着Ruby2.1稳定版的发布,Refinements特性解除了实验状态,意味着Ruby团队支持建议你去使用Refinements特性了,但是比较悲剧的是, Refinements的文档没有跟上,你要去看文档学习这个新特性的话,多半会出错。

下面我总结了一下Ruby2.1中Refinements的大致用法,如有遗漏,请告诉我。

一、普通青年使用Refinements的方式:

 
# refinements提供一种方法,让类的修改只影响到某个作用域

#判断一个字符串是不是数字型字符串
module NumberQuery
  refine String do
    def number?
      !!match(/^[0-9]+$/)
    end
  end
end

# 并不是定义了就能用的
class A
  def a(n)
    n.respond_to?(:number?)
  end
end

A.new.a "123"  #=> 这里会返回false,意味着没有定义number?方法

# 你必须用Module#using 方法
class A
  using NumberQuery 
end

# 你以为打开A类,using NumberQuery就可以了? 你太天真了。
A.new.a “123”  #=> false

# 看清楚,你必须重新定义A#a方法
class A
  using NumberQuery
  def a(n)
    n.respond_to?(:number?)  #=> true
  end
end

A.new.a "123" #=> 返回true,证明number?方法可以用了。

可见, 你使用了Module#refine方法打开类去增加的方法,只能使用Module#using方法在需要使用这个补丁的地方,引入补丁模块,才可以使用。而且,注意上面的示例代码,你必须在类定义的原始地方去using NumberQuery才起作用。这有点类似java或.net中的概念,Classboxes,即classbox的修改只对本classbox(或者导入它的 classbox)是可见的,这个特性我们称之为本地重绑定(local rebinding)。 C#里ms也有一个using,用于扩展方法, C#的扩展方法仅仅在其显式导入的代码中才是可见的, 这也和我们上面的Ruby示例相似。所以Ruby中的Refinements算是改进版的Classboxes了。

二、二逼青年使用Refinements的方式

 
module NumberQuery
  refine String do
    def number?
      !!match(/^[0-9]+$/)
    end
  end
end

然后:

 
class String
  using NumberQuery
  def other_method
    puts "hello" if number?
  end
end

当然,这不会造成String类全局的污染,只限于other_method方法,但是,请注意,你是不是有惯性的打开类进行Monkey Patches了?

有人可能这么用:

 
class T
  refine String do
    def number?
      !!match(/^[0-9]+$/)
    end
  end
end

哥,拜托,这样是会报错的:

 
  NoMethodError: undefined method `refine' for T:Class

这意味着,你不能在一个类中去使用refine。

还有人有点小聪明,他这么用:

 
# 我这个模块,不仅仅是打补丁的啊,还有其他方法
module NumberQuery
  refine String do
    def number?
      !!match(/^[0-9]+$/)
    end
  end
 
  def hello
    puts "world".number?
  end
end

# 那么我在class A中,除了using,还得include
class A
  using NumberQuery
  include NumberQuery
end

这样是行不通的, 还是去学学普通青年的用法吧,NumberQuery#hello方法中使用的number?是不合法的。

还有人在想,这多麻烦啊,还得写两遍模块的名字,有了,我用included方法:

 
module NumberQuery
  def self.included(base)
    base.send(:using, self)
  end
 
  refine String do
    def number?
      !!match(/^[0-9]+$/)
    end
  end
 
  def hello
    puts "world"
  end
end

# 这样,我就可以只include一次NumberQuery模块了。
class A
  include NumberQuery
end

打住吧,兄弟,你又犯二了,睁大眼睛看看报的什么错吧!

有位兄弟,想定义个类方法:

 
module NumberQuery
  refine String do
    def self.number?(str)
      !!str.match(/^[0-9]+$/)
    end
  end
end

class A
  using NumberQuery
  def a
    String.number?("123")
  end
end

A.new.a #=> NoMethodError: undefined method `number?' for String:Class

傻眼了吧? 呵呵,告诉你正确的定义类方法的用法:

 
module NumberQuery
  refine String.singleton_class do
    def number?(str)
      !!str.match(/^[0-9]+$/)
    end
  end
end

class A
  using NumberQuery
  def a
    String.number?("123")
  end
end

A.new.a #=> true

这次正常了。

Refinements还有个容易令Rubyist犯二的地方,就是你refine的方法,如果和类中的方法重名,还是会重写掉那个方法的,当然你可以也使用super。

 
module NumberQuery
  refine String do
    def to_s
      to_i
    end
  end
end

class A
  using NumberQuery
  def a
    "123".to_s
  end
end

String#to_s的方法被修改了,唉,难道这又是Monkey Patches的节奏? 不。 它只在A#a这个方法内有用,不会污染到全局,但是你在碰到类似情况的时候,一定要注意。当然,在A的子类,也会被传下去,除非a方法被重写。

 
class B < A; end
B.new.a #=> 123

#当子类B中,重写的a方法之后:
class B < A
  def a
    "123".to_s
  end
end
B.new.a #=> "123"

#除非你在B类也使用refine补丁
class B < A
  using NumberQuery
  def a
    "123".to_s
  end
end
B.new.a #=> 123

还有个值得说明的地方,就是,using的模块,不会被挂到继承树(祖先树/ancestors tree)上:

 
module NumberQuery
  refine String do
    def number?
      !!match(/^[0-9]+$/)
    end
  end
end

class A
  using NumberQuery
  def a
    "123".number?
  end
end

A.ancestors #=>  [A, Object, Kernel, BasicObject]

可以看到, NumberQuery并没有被挂到祖先树上。

到此,关于“Ruby2.1中Refinements特性有哪些”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


标题名称:Ruby2.1中Refinements特性有哪些
URL分享:http://hbruida.cn/article/pjidhg.html