• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

《Effective Ruby:改善Ruby程序的48条建议》一第13条:通过""操作符实现比 ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

本节书摘来自华章出版社《Effective Ruby:改善Ruby程序的48条建议》一书中的第2章,第2.8节,作者 [美]彼得 J.琼斯(Peter J. Jones),更多章节内容可以访问云栖社区“华章计算机”公众号查看

第13条:通过"<=>"操作符实现比较和比较模块

在第12条中提到了四种测试对象相等性的方法。如果你对对象的排序和比较有兴趣,那么你就需要进一步定义其他的比较操作符了。与等价操作符不同的是,类并没有从其他比较操作符中继承默认实现。还好,Ruby提供了一种简便的方式来实现它,这一点我们会在稍后讨论。
首先,让我们做一件有意思的事情,就是实现一个具有特殊序列的类。作为程序员,我们已经习惯了奇怪的版本号的定义,因此它们并不会给我们造成太多的困扰。但对外行来说就完全不同了。如何比较“10.10.3”和“10.9.8”这两个版本号呢?很明显,如果我们使用词典序来比较它们那就错了。为了得到正确的答案,你需要分别比较它们的每个部分。这就是接下来我们在Version类中要做的事情。
为了更清楚地表现,我们暂且处理仅含三个部分的版本号(就像前面描述的那样)。第一部分是主版本,接下来是次版本,最后是修订版本。同时,为了更加专注于版本号的比较,我们仅处理那些符合格式要求的版本号。这样,将一个用来描述版本的字符串解析成独立的几个部分的工作就变得简单多了。

我想再次强调,一般说来类不会自动继承比较操作符,但有一个例外。这一点我们会在之后讨论,现在你要做的就是为这个类定义一个比较操作符,也就是“<=>”操作符。这个特别的操作符其实继承自Object类,但是这个继承的实现却是不完整的。让我们看看如果试图对一组Version类的对象进行排序时会发生什么:

这对我们来说并没有什么帮助。要怪就怪“<=>”操作符的默认实现好了。它只考虑了两个对象是否相同(使用equal?和“===”操作符),却没有按我们的需要完整地进行比较。如果两个被比较的对象不相同就返回nil,从而告诉sort方法这个比较是非法的。但也还好,毕竟你不能对一个通用的实现抱有太大的期望,所以咱们还是自己动手实现一个吧。
完整地实现比较操作符需要两步。最难的一部分是编写一个合理的“<=>”操作符(通俗的说法是“太空舱”操作符)。要记住在Ruby语言中,二元操作符最终会被转换成方法调用的形式,左操作数对应着方法的接收者,右操作数对应着方法第一个也是唯一的那个参数。当编写一个比较操作符时,通常的做法是将参数命名为“other”,因为它代表着要比较的另一个对象。
由于“<=>”操作符的返回值非常灵活,因此它可以被看成全能的比较操作符。它可以返回如下四种情况:
当接收者和参数的比较无意义时,比较操作符理应返回nil。因为参数可能是另一个类的实例,甚至可以是nil。对于某些类来说,在比较之前将参数转换成正确的类型是很有用的,但通常情况下更好的做法是,当接收者和要比较的参数不是同一个类的实例时就直接返回nil。我们要实现的Version类就将这样做。
如果接收者比参数小,则返回-1。换句话说,如果使用“<”操作符比较的结果是真,那么使用“<=>”比较的结果就应该是一个负值。
如果接收者比参数大,则返回1。这就要用“>”操作符来解释了。如果使用“>”操作符比较的结果是真,那么使用“<=>”比较的结果就应该是一个正值。
如果接收者与参数相等,返回0。换句话说,只有当“<=>”返回值为0时,使用“==”比较的结果才是真。
我们想让Version类中的比较操作符和数值类中的比较操作符有一致的行为。事实上,我们可以像数值类一样实现我们的版本。你可以看到,它的实现遵循了前面提及的规则:

当编写“<=>”时,通常的做法是将比较方法代理给对象的实例变量。Version类中的三个变量都是Fixnum类的实例,这意味着它们都实现了可用的“<=>”操作符。这大大简化了我们的工作。为了比较版本号,我们需要考虑接收者(左操作数)中的实例变量以及那些参数中的实例变量(右操作数)从主版本到修订版本的顺序。一旦接收者中有一个变量与参数中对应的变量不相符,我们就可以停止比较。换句话说,如果要比较的两个版本的主版本号不相符,那么无需比较次版本号或修订版本号就已经可以知道谁大谁小。但是如果主版本号相同,就需要比较次版本号。以此类推,当次版本号也相同时就需要比较修订版本号。当所有部分和另一个都相同时,我们的比较操作符应该返回0以表示两个Version对象的等价性。而其他情况下,只需使用“<=>”操作符对第一组不匹配的变量进行操作,并将其结果返回。思考Version类中比较操作符的实现:

每个部分都会分别被比较,并将其比较的结果存放于一个数组中。我们需要做的仅仅是在这个数组中寻找第一个非零元素(第一对不相同的部分)。如果所有部分都相同,那么detect方法就会返回nil,而此时比较操作符返回0。现在,我们就可以对一组Version对象进行排序了。

为了完整地实现Version对象的比较功能,我们还需添加一段代码。除了排序之外,我们想让这些对象能够使用诸如“>”、“>=”这样的操作符。事实上,这五个操作符构成了完整的排序操作符,它们是:“<”、“<=”、“==”、“>”和“>=”。你如果知道我们无需自己手动实现它们一定会很高兴吧。我们要做的只是引入一个名为Comparable的模块。

就这么简单。现在我们可以使用所有的比较操作符以及一个额外的helper方法:

当使用Comparable模块时,还有些因素需要考虑一下。首先,对于某些类你可能想实现自己的“==”操作符,因此这比使用Comparable模块中的方法间接一些。在第12条中有一个很好的例子,其在做比较之前对数值类型进行了转换。如果你想这样做,你需要编写自己的等价操作符或者改变使“<=>”操作符返回0的条件。具体如何选择,应由你希望其他的比较运算符表现出什么样的行为来决定。
如果你想让“>=”、“<=”和“==”返回一致的结果,那么应该改变“<=>”计算相等性的方式。如果无需保持一致,简单地重载“==”就可以了,使其相较于其他比较运算符不那么严格。但是对于多数类,你可能希望使其所有运算符都相互一致。
最后,如果你希望类的实例能被作为哈希表的键来使用,你需要再做两件事情。首先,使“eql?”方法作为“==”操作符的别名。因为“eql?”的默认实现和“equal?”相同,如果不重新实现它会使类中的“<=>”操作符变得没有意义。而别名则使得哈希类使用由Comparable模块定义的“==”运算符来进行比较。
你还需要定义一个返回Fixnum类对象的哈希方法。为了使哈希类达到最好的性能,应该确保不同的对象返回不同的哈希值。下面是一个Version类的简单的示范实现(编写一个优化的哈希方法不在本书的讨论范围之内)。

要点回顾
通过定义“<=>”操作符和引入Comparable模块实现对象的排序。
如果左操作数不能与右操作数进行比较,“<=>”操作符应该返回nil。
如果要实现类的“<=>”运算符,应该考虑将eql?方法设置为“==”操作符的别名,特别是当你希望该类的所有实例可以被用来作为哈希键的时候,就应该重载哈希方法。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
如何快速正确的安装Ruby,Rails运行环境发布时间:2022-07-14
下一篇:
ruby楼层排序问题发布时间:2022-07-14
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap