Уже было несколько хороших ответов на вашу непосредственную проблему, но я заметил некоторые другие части вашего кода, которые заслуживают комментария. (Хотя большинство из них тривиальны.)
Вот четыре тривиальных, все они связаны со стилем кодирования:
- Отступ: вы смешиваете 4 пробела для отступа и 5 пробелов. Как правило, лучше придерживаться только одного стиля отступа, а в Ruby это обычно 2 пробела.
- Если метод не принимает никаких параметров, обычно не указывать в описании метода парантезы.
- Аналогично, если вы отправляете сообщение без аргументов, парантезы прекращаются.
- Нет пробелов после первого и перед закрывающим паратезом, кроме блоков.
Во всяком случае, это просто мелочи. Большой материал это:
def new
@a = 1
end
Это не делать то, что вы думаете, что делает! Это определяет экземпляр метод с именем X#new
и , а не метод класса с именем X.new
!
То, что вы здесь звоните:
x = X.new
- это метод класса с именем new
, который вы унаследовали от класса Class
. Таким образом, вы никогда не вызываете свой новый метод, что означает, что @a = 1
никогда не будет выполнено, что означает, что @a
всегда неопределен, что означает, что он всегда будет иметь значение nil
, что означает @a
из self
и @a
из other
всегда будет одинаковым, что означает m
всегда будет true
!
Что вы, вероятно, хотите сделать, это предоставить конструктор, за исключением того, что у Ruby нет конструкторов . Ruby использует только фабричные методы.
Метод, который вы действительно хотели переопределить, - это экземпляр метод initialize
. Теперь вы, вероятно, спрашиваете себя: «Почему я должен переопределять экземпляр метод с именем initialize
, когда я на самом деле вызываю class метод с именем new
?"
Итак, построение объекта в Ruby работает следующим образом: построение объекта разделено на две фазы: выделение и инициализация . Распределение выполняется с помощью общедоступного метода класса allocate
, который определяется как метод экземпляра класса Class
и обычно никогда не переопределяется. Он просто выделяет пространство памяти для объекта и устанавливает несколько указателей, однако в данный момент объект на самом деле непригоден для использования.
Вот где приходит инициализатор: это метод экземпляра initialize
, который устанавливает внутреннее состояние объекта и переводит его в согласованное, полностью определенное состояние, которое может использоваться другими объектами.
Итак, чтобы полностью создать новый объект, вам нужно сделать следующее:
x = X.allocate
x.initialize
[Примечание: программисты Objective-C могут распознать это.]
Однако, поскольку слишком легко забыть вызвать initialize
и, как правило, объект должен быть полностью действительным после создания, существует удобный фабричный метод, называемый Class#new
, который выполняет всю эту работу за вас и выглядит примерно так:
class Class
def new(*args, &block)
obj = alloc
obj.initialize(*args, &block)
return obj
end
end
[Примечание: на самом деле initialize
является частным, поэтому необходимо использовать отражение, чтобы обойти ограничения доступа следующим образом: obj.send(:initialize, *args, &block)
]
Наконец, позвольте мне объяснить, что не так в вашем m
методе. (Остальные уже объяснили, как ее решить.)
В Ruby нет способа (примечание: в Ruby слово «нет пути» фактически переводится как «всегда есть способ, связанный с отражением») для доступа к переменной экземпляра извне экземпляра. Вот почему она называется переменной экземпляра, потому что она принадлежит экземпляру. Это наследие от Smalltalk: в Smalltalk нет ограничений по видимости, все методы являются общедоступными. Таким образом, переменные экземпляра - это only способ инкапсуляции в Smalltalk, и, в конце концов, инкапсуляция является одним из столпов OO. В Ruby есть ограничения видимости (как мы видели, например, выше), поэтому нет необходимости скрывать переменные экземпляра по этой причине. Однако есть и другая причина: принцип единого доступа.
UAP заявляет, что способ использования функции должен быть независимым от того, как реализована функция . Таким образом, доступ к функции всегда должен быть одинаковым, то есть единообразным. Причина этого заключается в том, что автор функции может свободно изменять внутреннюю работу функции, не нарушая пользователей этой функции. Другими словами, это базовая модульность.
Это означает, например, что получение размера коллекции всегда должно быть одинаковым, независимо от того, хранится ли размер в переменной, динамически вычисляется каждый раз, лениво вычисляется в первый раз, а затем сохраняется в переменной, запоминается или без разницы. Звучит очевидно, но, например, Ява понимает это неправильно:
obj.size # stored in a field
против
obj.getSize() # computed
Руби выбирает легкий путь. В Ruby есть только один способ использования функции: отправка сообщения. Поскольку существует только один путь, доступ к нему тривиален.
Итак, короче говоря: вы просто не можете получить доступ к переменной экземпляра другого экземпляра. вы можете взаимодействовать с этим экземпляром только посредством отправки сообщений. Это означает, что другой объект должен либо предоставить вам метод (в данном случае по крайней мере protected
видимости) для доступа к его переменной экземпляра, либо вы должны нарушить инкапсуляцию этого объекта (и, таким образом, потерять Uniform Access, увеличить соединение и риск поломки в будущем) с помощью отражения (в данном случае instance_variable_get
).
Вот оно, во всей красе:
#!/usr/bin/env ruby
class X
def initialize(a=1)
@a = a
end
def m(other)
@a == other.a
end
protected
attr_reader :a
end
require 'test/unit'
class TestX < Test::Unit::TestCase
def test_that_m_evaluates_to_true_when_passed_two_empty_xs
x, y = X.new, X.new
assert x.m(y)
end
def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes
assert X.new('foo').m(X.new('foo'))
end
end
Или альтернативно:
class X
def m(other)
@a == other.instance_variable_get(:@a)
end
end
Какой из этих двух вы выбрали - вопрос личного вкуса, я бы сказал. Класс Set
в стандартной библиотеке использует версию отражения, хотя он использует instance_eval
вместо:
class X
def m(other)
@a == other.instance_eval { @a }
end
end
(Понятия не имею, почему. Может быть, instance_variable_get
просто не существовало, когда было написано Set
. В феврале Руби исполнится 17 лет, некоторые вещи в stdlib - с самых ранних дней .)