我需要创建一个类如下(真正的问题更复杂)
class IArray
attr_reader :array
# array is a Ruby Array
def initialize(array)
@array = array
end
def method_missing(...)
# forwards every call to @array
end
end
现在在我的代码中我想做
a1 = [1, 2, 3]
a2 = IArray.new([4, 5])
a1.concat(a2)
最后一个语句不起作用,说“没有将a2隐式转换为数组”。 a1
如何知道a2
不是数组?我已为is_a?
实施kind_of?
和a2
,以便在询问是否为数组时返回tru
e。我想要的是a1
认为a2
是一个数组,然后调用a2
上需要进行合并的任何方法。
我希望任何其他类都能发生同样的情况,即只需将它包装在一个类中,但让它像没有被包装一样工作。
答案 0 :(得分:2)
不,不幸的是,在Ruby中,一个对象无法完全模拟另一个对象。这是一个不幸的限制,因为一个模拟另一个的对象是OO的基石之一,而Ruby是一种OO语言,所以这应该是可行的。
有三个主要原因导致不可能:
BasicObject#equal?
)打破模拟,从而打破OO。通过比较Reference Equality,您可以将模拟对象与模拟对象区分开来,这是不可能的。您可以使用BasicObject
修补程序来删除或替换equal?
,但这可能会使事情发生左右分裂。nil
和false
falsy 。无法编写自己的 falsy 对象,因此无法模拟nil
或false
。你可以走得很远,但是:
nil
或false
。&
操作员调用to_proc
,一元前缀星号*
" splat"运算符调用to_a
,print
调用to_s
,Array#[]
调用to_int
,等等上)。另外,至少在Numeric
s,有一个定义的强制协议。但是,在您的特定情况下,您遇到的情况之一就是它无法正常工作:而to_ary
可让您在制作模拟Array
的内容方面有很长的路要走在您的情况下,您需要进一步跟踪您的IArray
,但当然将其转换为Array
会丢失其身份及其附加行为。不幸的是,你搞砸了。你无能为力。
在理想的OO世界中,两个使用相同协议的对象应该被认为是相同的类型,ergo Array#concat
不应该关心它的参数是否是{{的实例1}}(或者可以转换为1),而是它的参数是否与Array
说明相同的协议(或更确切地说:说Array
实际的协议的子集要求)。
我只能推测为什么Ruby在这种情况下不遵循OO范例:性能。在OO中,一个对象永远不会知道另一个对象的表示,即使这两个对象属于同一类型(或同一类)。这是基于抽象数据类型的面向对象数据抽象和数据抽象之间的根本区别:ADT实例可以检查相同类型的其他实例的表示,对象可以不 任何其他对象的表示形式,即使它具有相同的类型(或类)。
然而,这意味着操作不可能同时检查两个对象的表示(该操作是第三个对象的方法,这意味着它不能检查任何一个对象的表示,或者它是两个对象之一的方法,在这种情况下,它可以检查自己的对象的表示而不是其他对象,这意味着在OO中编写需要访问的对象是不可能的。一次表示两个对象。
E.g。连接两个链接列表是O(1),如果您有权访问第一个列表的最后一个元素的concat
指针,那么第二个列表的第一个元素是{{1} {1}}指针,但在OO中,您最多可以访问其中一个(除非这两个列表显式公开了一个允许访问这两个指针的公共方法)。将数组连接到另一个数组需要快速访问两个数组的内部表示,因此Ruby决定在这里破坏OO封装,并且要求两个对象都是一个知道内部内存布局的类。
这是不幸的,而不是纯粹的OO,但这是一个权衡,甚至"硬核OO"像Smalltalk这样的语言可以用于某些核心数据类型。 (例如数字,字符串,数组和布尔值。)
在YARV,JRuby等实现中,核心库的重要部分通过对实现内部的特权访问来实现,还有另一个问题,因为它非常诱人(并且没有为了更方便的实现,核心方法绕过Ruby语义的方法可以防止这种情况发生。一个完全不相关的例子:实现各种复杂的"重载" C中的YARV中的next
或Java中的JRuby中的prev
很容易:在YARV中,C函数具有对解释器内部的特权访问,因此可以检查以某人试图重新实现的方式传递的参数。 Ruby中的方法不能,在JRuby中,有一些胶水魔法允许你将这些重载方法实现为实际的Java重载方法,以获得更多便利。
同样,由于所有核心方法都具有对实现的GC内存中对象的内部表示的特权访问,因此它们通常会通过直接检查其内存中的表示来检查对象的类,而不是通过{ {1}},Enumerable#inject
,class
或is_a?
。
答案 1 :(得分:1)
看看SimpleDelegator。我认为这将满足您的需求。
答案 2 :(得分:0)
这是我的(可能是问题的不完整解决方案)。为了使其工作,所有ruby对象必须在RBProxyObject中“打包”,如下所示:
class RBProxyObject
attr_reader :ruby_obj
def initialize(ruby_obj)
@ruby_obj = ruby_obj
end
def is_a?(klass)
@ruby_obj.is_a?(klass)
end
def kind_of?(klass)
@ruby_obj.is_a?(klass)
end
def method_missing(symbol, *args, &blk)
begin
@ruby_obj.send(symbol, *args, &blk)
rescue TypeError
args[0].native(symbol, @ruby_obj, &blk)
end
end
def native(*args)
method = args.shift
other = args.shift
other.send(method, @ruby_obj, *args)
end
end
现在应该如何使用它:
a1 = [1, 2, 3]
a2 = [4, 5]
p1 = RBProxyObject.new(a1)
p2 = RBProxyObject.new(a2)
> p p1[0]
1
> p p2[1]
5
> p p1.is_a? Array
true
> p p1.concat(p2)
[1, 2, 3, 4, 5]
应该在这个类中添加哪些其他方法以及它可以在哪里破解?感谢...