模仿另一个Ruby类,所以对象通过===类型检查

前端之家收集整理的这篇文章主要介绍了模仿另一个Ruby类,所以对象通过===类型检查前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我想创建一个充当特定类的对象,例如Fixnum,但不是该类的实例和它的子类.

有各种各样的用例.在Fixnum的情况下,我想定义一个更具体的整数类型,它本质上是一个Fixnum,但也实现了一些额外的逻辑.我不能子类Fixnum本身,因为立即类型如Fixnum和Symbol不能被子类化.

另一个用例是在自动化测试中嘲笑:有时您想要创建一个像某个类(通常是一个模型实例)的对象,但是由于技术原因而不是该类的实例.

以下是如何创建将所有方法委托给内部存储的fixnum的特定整数类型:

require 'delegate'
require 'forwardable'

# integer representing a page number
class PageNumber < DelegateClass(Integer)
  extend Forwardable

  def initialize(value,name)
    @name = name
    super(value)
  end

  def inspect
    "#{@name} #{to_i}"
  end

  alias_method :to_i,:__getobj__
  def_delegators :to_i,:instance_of?,:kind_of?,:is_a?
end

这个对象可以通过is_a?和类似的检查:

page = PageNumber.new(1,"page")
page.is_a? Fixnum  #=> true

但是没有办法可以让它通过Module#===型检查:

# my problem:
Fixnum === page    #=> false

事实上,我的对象没有这个检查是非常不幸的,因为===方法在内部使用case语句:

case page
when Fixnum
  # it will never get here
when String
  # ...
else
  # ...
end

我的问题是如何创建一个通过===检查而不增加内置类的===方法的模拟类型?

解决方法

如果我们在谈论MRI1,答案很简单:你不能.

模块#===方法实际上是rb_obj_is_kind_ofrb_obj_is_kind_of C API方法.后者的实现很短,我将其粘贴到这里:

VALUE
rb_obj_is_kind_of(VALUE obj,VALUE c)
{
    VALUE cl = CLASS_OF(obj);

    /* Type checking of `c' omitted */

    while (cl) {
    if (cl == c || RCLASS_M_TBL(cl) == RCLASS_M_TBL(c))
        return Qtrue;
    cl = RCLASS_SUPER(cl);
    }
    return Qfalse;
}

可以看到,这种方法遍历被检查对象的祖先,并以两种方式进行比较:首先,它检查祖先是否与传递的模块相同,然后检查它们是否具有相同的方法表.

后一个检查是必需的,因为Ruby中包含的模块似乎被插入到继承链中,但是由于一个模块可能被包含在其他几个模块中,所以它不是插入到链中的真实模块,而是代理对象,常数和方法表指向原始模块.

例如,我们来看看Object的祖先:

ruby-1.9.2-p136 :001 > Object.ancestors
 => [Object,Kernel,BasicObject] 
ruby-1.9.2-p136 :002 > Object.ancestors.map { |mod| Object.new.is_a? mod }
 => [true,true,true]

在这里,Object和BasicObject将被第一次检查成功比较,而Kernel由第二个检查成功比较.

即使您尝试使用(扩展名为C)的代理对象来尝试欺骗rb_obj_is_kind_of方法,它将需要具有与实际Fixnum相同的方法表,这将有效地包括所有Fixnum的方法.

1我调查了Ruby 1.9的内部,但是它们在1.8中的行为完全相同.

猜你在找的Ruby相关文章