千家信息网

Ruby2.1中Refinements特性有哪些

发表于:2024-11-28 作者:千家信息网编辑
千家信息网最后更新 2024年11月28日,这篇文章主要介绍"Ruby2.1中Refinements特性有哪些",在日常操作中,相信很多人在Ruby2.1中Refinements特性有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作
千家信息网最后更新 2024年11月28日Ruby2.1中Refinements特性有哪些

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

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  endend# 并不是定义了就能用的class A  def a(n)    n.respond_to?(:number?)  endendA.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  endendA.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  endend

然后:

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

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

有人可能这么用:

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

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

  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?  endend# 那么我在class A中,除了using,还得includeclass A  using NumberQuery  include NumberQueryend

这样是行不通的, 还是去学学普通青年的用法吧,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"  endend# 这样,我就可以只include一次NumberQuery模块了。class A  include NumberQueryend

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

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

module NumberQuery  refine String do    def self.number?(str)      !!str.match(/^[0-9]+$/)    end  endendclass A  using NumberQuery  def a    String.number?("123")  endendA.new.a #=> NoMethodError: undefined method `number?' for String:Class

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

module NumberQuery  refine String.singleton_class do    def number?(str)      !!str.match(/^[0-9]+$/)    end  endendclass A  using NumberQuery  def a    String.number?("123")  endendA.new.a #=> true

这次正常了。

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

module NumberQuery  refine String do    def to_s      to_i    end  endendclass A  using NumberQuery  def a    "123".to_s  endend

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

class B < A; endB.new.a #=> 123#当子类B中,重写的a方法之后:class B < A  def a    "123".to_s  endendB.new.a #=> "123"#除非你在B类也使用refine补丁class B < A  using NumberQuery  def a    "123".to_s  endendB.new.a #=> 123

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

module NumberQuery  refine String do    def number?      !!match(/^[0-9]+$/)    end  endendclass A  using NumberQuery  def a    "123".number?  endendA.ancestors #=>  [A, Object, Kernel, BasicObject]

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

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

0