other.x[i]*=self
和
一样
other.x[i] = other.x[i] * self
或者让它变得非常明确
other.x().[]=(i)(other.x().[](i).*(self))
other.x[i] 是 Integer,self 也是 Integer(在本例中为 3)。
在你的Integer 方法中,你调用other.x,但是当你乘以some_integer * 3,然后在你的Integer#* 方法中,other 是3,所以你调用的是3.x,它确实不存在.
请注意,您的 Vektor#* 方法现在也已损坏,因为它也需要将两个 Integers 相乘,但您只是重写了知道如何将两个 Integers 相乘的 Integer#* 方法(以及实际上也知道如何将Integer 与正确实现Numeric#coerce 协议的任何对象 相乘)与只知道如何将Integer 和Vektor 相乘的对象,因此没有更知道如何将两个Integers 相乘。
另一个问题是,在您的Vektor#* 方法中,您检查other 是否是Numeric 的一个实例,但反过来您只为Integers 实现乘法。这使您的乘法不对称,例如some_vektor * 1.0 可以,但1.0 * some_vektor 不行。
正确的实现是完全不接触Integer,而只是实现数字强制协议。这将解决您的所有问题:您不必对 anything 进行猴子补丁,并且您的 Vektors 将自动与 any Numeric 一起工作库、标准库、Ruby 生态系统,甚至还有一些尚未编写好的库。像这样的:
class Vektor
def self.inherited(*)
raise TypeError, "#{self} is immutable and cannot be inherited from."
end
def initialize(*vektors)
self.x = if vektors.size.zero?
Array.new(3, 0)
else
vektors
end.freeze
end
singleton_class.alias_method :[], :new
alias_method :length, def size
x.size
end
def coerce(other)
[self, other]
end
def *(other)
case other
when Vektor
raise ArgumentError, "the vektors don't have the same length!" if size != other.size
x.zip(other.x).map {|a, b| a * b }.sum
when Numeric
self.class.new(*x.map(&other.method(:*)))
else
a, b = other.coerce(self)
a * b
end
end
protected
attr_reader :x # `x` should not be writeable by anybody!
private
attr_writer :x
freeze
end
您会注意到我对您的代码做了一些更改:
-
Vektors 现在是不可变的,Vektor#* 返回一个新的Vektor,而不是改变self。尽可能保持对象不可变通常是个好主意,但对于“类似数字”的对象,例如Vektors,尤其非常重要(并且确实是预期的)。如果2 * 3 没有返回6 而是让2 具有6 的值,您会感到非常惊讶,不是吗?但这正是您的代码对 Vektors 所做的事情!
-
x 不再向所有人公开,最重要的是,不再为所有人可写。
- 我用更高级别的迭代构造替换了所有循环。作为一般规则,如果您在 Ruby 中编写循环,那么您做错了什么。
Enumerable 中有很多强大的方法,你永远不需要循环。
我还编写了一些测试来证明即使不触及Vektor 之外的任何 类,我们现在也支持int * vek、vek * int、float * vek、vek * float 的乘法, rational * vek、vek * rational、complex * vek 和 vek * complex,只需实现 Numeric#coerce 强制协议即可。
require 'test/unit'
class VektorTest < Test::Unit::TestCase
def test_that_creating_an_empty_vektor_actually_creates_a_zero_vektor_of_dimension_3
v = Vektor.new
assert_equal 3, v.size
end
def test_that_square_brackets_is_an_alias_for_new
v = Vektor[]
assert_equal 3, v.size
end
def test_that_we_can_multiply_two_trivial_vektors
v1 = Vektor[2]
v2 = Vektor[3]
assert_equal 6, v1 * v2
end
def test_that_we_can_multiply_two_nontrivial_vektors
v1 = Vektor[2, 3, 4]
v2 = Vektor[5, 6, 7]
assert_equal 56, v1 * v2
end
def test_that_we_can_multiply_a_trivial_vektor_with_an_integer
v = Vektor[2]
assert_equal Vektor[6], v * 3 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3 }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_an_integer
v = Vektor[2, 3, 4]
assert_equal Vektor[6, 9, 12], v * 3 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3 }
end
def test_that_we_can_multiply_an_integer_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6], 3 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_an_integer_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3 * v }
end
def test_that_we_can_multiply_an_integer_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6, 9, 12], 3 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_an_integer_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3 * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_float
v = Vektor[2]
assert_equal Vektor[6.0], v * 3.0 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_float_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3.0 }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_float
v = Vektor[2, 3, 4]
assert_equal Vektor[6.0, 9.0, 12.0], v * 3.0 # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_float_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3.0 }
end
def test_that_we_can_multiply_a_float_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6.0], 3.0 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_float_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3.0 * v }
end
def test_that_we_can_multiply_a_float_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6.0, 9.0, 12.0], 3.0 * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_float_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3.0 * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_rational
v = Vektor[2]
assert_equal Vektor[6r], v * 3r # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_rational_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3r }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_rational
v = Vektor[2, 3, 4]
assert_equal Vektor[6r, 9r, 12r], v * 3r # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_rational_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3r }
end
def test_that_we_can_multiply_a_rational_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6r], 3r * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_rational_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3r * v }
end
def test_that_we_can_multiply_a_rational_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6r, 9r, 12r], 3r * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_rational_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3r * v }
end
def test_that_we_can_multiply_a_trivial_vektor_with_a_complex_number
v = Vektor[2]
assert_equal Vektor[6i], v * 3i # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_trivial_vektor_with_a_complex_number_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { v * 3i }
end
def test_that_we_can_multiply_a_nontrivial_vektor_with_a_complex_number
v = Vektor[2, 3, 4]
assert_equal Vektor[6i, 9i, 12i], v * 3i # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_nontrivial_vektor_with_an_complex_number_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { v * 3i }
end
def test_that_we_can_multiply_a_complex_number_with_a_trivial_vektor
v = Vektor[2]
assert_equal Vektor[6i], 3i * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_complex_number_with_a_trivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2]
assert_nothing_raised { 3i * v }
end
def test_that_we_can_multiply_a_complex_number_with_a_nontrivial_vektor
v = Vektor[2, 3, 4]
assert_equal Vektor[6i, 9i, 12i], 3i * v # this will fail because you haven't implemented equality!
end
def test_that_multiplying_a_complex_number_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
v = Vektor[2, 3, 4]
assert_nothing_raised { 3i * v }
end
end
让我们还添加一些您通常需要实现的方法,以使您的对象与 Ruby 生态系统的其他部分一起工作:
class Vektor
def ==(other)
x == other.x
end
def eql?(other)
other.is_a?(Vektor) && self == other
end
def hash
x.hash
end
def to_s
"(#{x.join(', ')})"
end
def inspect
"Vektor#{x.inspect}"
end
end
如果您绝对必须使用猴子补丁,那么重要的是您保留访问您正在猴子补丁的旧版本的方法。执行此操作的工具是Module#prepend 方法。它看起来像这样:
class Vektor
def self.inherited(*)
raise TypeError, "#{self} is immutable and cannot be inherited from."
end
attr_reader :x # `x` should not be writeable by anybody!
def initialize(*vektors)
self.x = if vektors.size.zero?
Array.new(3, 0)
else
vektors
end.freeze
end
singleton_class.alias_method :[], :new
alias_method :length, def size
x.size
end
def *(other)
case other
when Vektor
raise ArgumentError, "the vektors don't have the same length!" if size != other.size
x.zip(other.x).map {|a, b| a * b }.sum
when Numeric
self.class.new(*x.map(&other.method(:*)))
end
end
private
attr_writer :x
freeze
end
(大部分相同,但在case 表达式中没有coerce 方法和else 子句,并且x 读取器需要为public。)
module IntegerTimesVektorExtension
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
super
end
end
因为猴子补丁核心类确实很危险,我们使用细化,以确保猴子补丁仅在您显式激活细化时才有效using IntegerTimesVektorRefinement。
module IntegerTimesVektorRefinement
refine Integer do
prepend IntegerTimesVektorExtension
end
end
现在,我们可以这样做:
v = Vektor[2, 3, 4]
5 * v
# `*': Vektor can't be coerced into Integer (TypeError)
using IntegerTimesVektorRefinement
5 * v
#=> <#<Vektor:0x00007fcc88868588 @x=[10, 15, 20]>
In the dark old times, before Module#prepend existed, we had to resort to other dirty tricks 能够保留猴子补丁方法。但是,从 2013 年 2 月 24 日发布的 Ruby 2.0(包括 Module#prepend)开始,这些技巧不再需要,不应使用,也不应教授。这包括alias_method 链式技巧,如下所示:
class Integer
alias_method :original_mul, :*
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
original_mul(other)
end
end
但是,如前所述:您不应该这样做。
最好的解决方案是实现coerce 协议。真的不是“只是”最好的解决方案,而是唯一正确的解决方案。
如果出于某种原因您不想实现coerce 协议,那么最好的解决方案是Module#prepend。理想情况下需要优化,但请注意并非所有 Ruby 实现都实现了优化。
如果你真的,真的,真的,必须做猴子补丁,并且正在使用一个十年前的、不受支持、未维护、过时、过时的 Ruby 版本,因此不能使用Module#prepend,那么有 仍然 better solutions out there than alias_method,例如获取实例方法作为Method 对象并将其存储在您关闭的局部变量中。
这里没有必要使用alias_method,如果不是完全错误,也是不好的、过时的、过时的做法。