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#*
теперь также не работает, потому что ему тоже нужно умножить два Integer
s, но вы просто переписали метод Integer#*
, который знает, как умножить два * 1027. * s (и на самом деле также знает, как умножить Integer
на любой объект , который правильно реализует протокол Numeric#coerce
) на тот, который знает только, как умножить Integer
и Vektor
, и, следовательно, больше не знает, как умножить два Integer
с.
Другая проблема заключается в том, что в вашем методе Vektor#*
вы проверяете, является ли other
экземпляром Numeric
, но тогда наоборот вы можете реализовать умножение только для Integer
с. Это делает вашу асимметрию умножения c, например, some_vektor * 1.0
будет работать, но 1.0 * some_vektor
не будет.
Правильная реализация будет не касаться Integer
вообще , и просто реализуйте протокол принуждения Numberri c. Это решит все ваши проблемы: вам не нужно ничего исправлять что-нибудь , и ваши Vektor
будут автоматически работать с 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
Вы заметите, что я внес некоторые изменения в ваш код:
Vektor
s теперь неизменны и Vektor#*
возвращает новый Vektor
вместо того, чтобы мутировать self
. Как правило, хорошей идеей является сохранение неизменяемости ваших объектов, насколько это возможно, но это особенно важно (и ожидается, действительно) для "числовых" объектов, таких как Vektor
s. Вы были бы ужасно удивлены, если бы 2 * 3
не вернул 6
, а сделал бы 2
значение 6
, не так ли? Но это именно то, что ваш код делал с Vektor
s! x
, больше не доступен всем, и, самое главное, больше не доступный для записи всем. - Я заменил все ваши циклы итерационными конструкциями более высокого уровня. Как правило, если вы пишете oop в Ruby, вы делаете что-то не так. В
Enumerable
есть так много мощных методов, что вам никогда не понадобится al oop.
Я также написал несколько тестов, чтобы продемонстрировать, что даже не касаясь любого класс за пределами 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
(в основном идентично, но без метода coerce
и без предложения else
в выражении case
, а читатель x
должен быть public
.)
module IntegerTimesVektorExtension
def *(other)
return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
super
end
end
И поскольку базовые классы для monkey-patching действительно опасны, мы используем уточнение , чтобы убедиться, что monkey-patch исправна только там, где вы явно активировать уточнение с помощью 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]>
В старые темные времена, до того, как Module#prepend
существовал, нам пришлось прибегнуть к другим грязным уловкам , чтобы иметь возможность сохранить метод исправления обезьян. Однако по состоянию на выпуск Ruby 2. 0 24 февраля 2013 года, который включает 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
, тогда есть все еще лучшие решения, чем alias_method
, такие как захват метода экземпляра как Method
объект и сохранение его в локальной переменной, которую вы закрываете.
Использование alias_method
здесь не обязательно, если не прямо неправильно , и плохая, устаревшая, устаревшая практика.