假设我有这个简单的类:
class Color
attr_accessor :rgb
def initialize(ary)
@rgb = ary
end
def +(other)
other = Color.new(other) unless Color === other
Color.new(@rgb.zip(other.rgb).map {|p| [p.reduce(:+), 255].min })
end
end
我知道这是实施它的一种不好的方法,但这是我能想到的最短路。
c100 = Color.new([100, 100, 100])
c100 + c100 #=> Color(200, 200, 200)
c100 + c100 + c100 #=> Color(255, 255, 255)
如果我将数组作为颜色:
,它也有效c100 + [50, 50, 50] #=> Color(150, 150, 150)
但我不能这样:
[50, 50, 50] + c100 #=> TypeError: can't convert Color into Array
定义coerce
不起作用。我怎样才能让它发挥作用?
答案 0 :(得分:3)
这是因为代码
[50, 50, 50] + c100
在Array上调用+
方法,而不是Color,并且该方法无法将颜色转换为数组。
相比之下,
c100 + [50, 50, 50]
会调用Color的+
方法。
但是,即使您在Color中定义转换方法:
class Color
def to_ary
return @rgb
end
end
Array方法无法正常工作;结果将是两个数组的串联,因为Array的+
方法连接它们的操作数,而不是添加它们的元素:
irb>[50,50,50]+c100
=> [50,50,50,100,100,100]
这里,结果将是一个数组,而不是一个颜色。
编辑:
我看到的唯一方法是使用Array的+
方法来处理接收Color作为第二个操作数的特殊情况。但是,我承认这种做法相当丑陋。
class Array
alias color_plus +
def +(b)
if b.is_a?(Color)
return b+self
end
return color_plus(b)
end
end
答案 1 :(得分:1)
进一步阐述@ peter-o的答案我想出了这个实现,虽然uggly在某种意义上说它重新定义了几种Array的方法,但它几乎成了预期行为的好方法,我不知道我想我会把它放在生产代码中,但我真的很喜欢这个挑战...很抱歉颜色主题有所不同但我不知道减去和时间的预期行为是什么。
class Array
alias :former_plus :+
alias :former_minus :-
alias :former_times :*
def +(other)
former_plus(other)
rescue TypeError
apply_through_coercion(other, :+)
end
def -(other)
former_minus(other)
rescue TypeError
apply_through_coercion(other, :-)
end
def *(other)
former_times(other)
rescue TypeError
apply_through_coercion(other, :*)
end
# https://github.com/ruby/ruby/blob/ruby_1_9_3/lib/matrix.rb#L1385
def apply_through_coercion(obj, oper)
coercion = obj.coerce(self)
raise TypeError unless coercion.is_a?(Array) && coercion.length == 2
coercion[0].public_send(oper, coercion[1])
rescue
raise TypeError, "#{obj.inspect} can't be coerced into #{self.class}"
end
private :apply_through_coercion
end
其中一个挑战是确保Point#-
方法上的反向调用不会返回意外结果,因此@coerced
实例变量作为对象上的控制标志。
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y, @coerced = x, y, false
end
def coerce(other)
@coerced = true
[self, other]
end
def coerced?; @coerced end
def +(other)
other = Point.new(*other) if other.respond_to? :to_ary
Point.new(@x + other.x, @y + other.y)
end
def -(other)
other = Point.new(*other) if other.respond_to? :to_ary
if coerced?
@coerced = false; other + (-self)
else self + (-other) end
end
def -@; Point.new(-@x, -@y) end
def *(other)
case other
when Fixnum then Point.new(@x*other, @y*other)
when Point then Point.new(@x*other.x, @y*other.y)
when Array then self * Point.new(*other)
end
end
end
毕竟,这段代码能够实现的目的是向不存在的Array类添加强制功能,明确地向方法Array#+
,Array#-
和Array#*
添加。