Уже было несколько хороших ответов на вашу непосредственную проблему, но я заметил некоторые другие фрагменты вашего кода, которые требуют комментария. (Хотя большинство из них тривиальны.)
Вот четыре тривиальных, и все они связаны со стилем кодирования:
- Отступ: вы смешиваете 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
, если на самом деле я вызываю метод класса с именем 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 нет ограничений видимости, все методы общедоступны. Таким образом, переменные экземпляра являются единственным способом инкапсуляции в Smalltalk, и, в конце концов, инкапсуляция является одним из столпов объектно-ориентированного программирования. В Ruby существуют существуют ограничения видимости (как мы видели, например, выше), поэтому по этой причине нет строгой необходимости скрывать переменные экземпляра. Однако есть и другая причина: Единый принцип доступа.
В UAP указано, что то, как использовать функцию, не должно зависеть от того, как эта функция реализована. Таким образом, доступ к функции всегда должен быть одинаковым, то есть унифицированным. Причина этого в том, что автор функции может свободно изменять то, как эта функция работает внутри, не нарушая работу пользователей этой функции. Другими словами, это базовая модульность.
Это означает, например, что получение размера коллекции всегда должно быть одним и тем же, независимо от того, хранится ли размер в переменной, вычисляется ли динамически каждый раз, лениво вычисляется в первый раз, а затем сохраняется в переменной, запоминается или что-то еще. Звучит очевидно, но, например. Java ошибается:
obj.size # stored in a field
vs.
obj.getSize() # computed
Руби выбирает легкий путь. В Ruby есть только один способ использования функции: отправить сообщение. Поскольку есть только один путь, доступ тривиально един.
Итак, если коротко: вы просто не можете получить доступ к переменной экземпляра другого экземпляра. вы можете взаимодействовать с этим экземпляром только через отправку сообщений. Это означает, что другой объект должен либо предоставить вам метод (в данном случае по крайней мере protected
видимости) для доступа к своей переменной экземпляра, либо вы должны нарушить инкапсуляцию этого объекта (и, таким образом, потерять унифицированный доступ, увеличить связанность и рискнуть в будущем). поломка) с помощью отражения (в данном случае 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
. В феврале Ruby исполнится 17 лет, кое-что в stdlib взято с самых ранних дней.)
person
Jörg W Mittag
schedule
02.12.2009
@a == other.a
, и переводчик сказал:NoMethodError: undefined method
a' для #‹X:0x1011bbcd0 @a=1›` - person OscarRyz   schedule 02.12.2009@a
— это переменная экземпляра. - person Andrew Grimm   schedule 28.03.2012