Можно ли сравнить частные атрибуты в Ruby? - PullRequest
3 голосов
/ 02 декабря 2009

Я думаю:

class X
    def new()
        @a = 1
    end
    def m( other ) 
         @a == other.@a
    end
end

x = X.new()
y = X.new()
x.m( y ) 

Но это не работает.

Сообщение об ошибке:

syntax error, unexpected tIVAR

Как я могу сравнить два личных атрибута из одного класса?

Ответы [ 4 ]

11 голосов
/ 02 декабря 2009

Уже было несколько хороших ответов на вашу непосредственную проблему, но я заметил некоторые другие части вашего кода, которые заслуживают комментария. (Хотя большинство из них тривиальны.)

Вот четыре тривиальных, все они связаны со стилем кодирования:

  1. Отступ: вы смешиваете 4 пробела для отступа и 5 пробелов. Как правило, лучше придерживаться только одного стиля отступа, а в Ruby это обычно 2 пробела.
  2. Если метод не принимает никаких параметров, обычно не указывать в описании метода парантезы.
  3. Аналогично, если вы отправляете сообщение без аргументов, парантезы прекращаются.
  4. Нет пробелов после первого и перед закрывающим паратезом, кроме блоков.

Во всяком случае, это просто мелочи. Большой материал это:

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 - с самых ранних дней .)

8 голосов
/ 02 декабря 2009

Есть несколько методов

Getter:

class X
  attr_reader :a
  def m( other )
    a == other.a
  end
end

instance_eval

class X
  def m( other )
    @a == other.instance_eval { @a }
  end
end

instance_variable_get:

class X
  def m( other )
    @a == other.instance_variable_get :@a
  end
end

Я не думаю, что у ruby ​​есть понятие «друга» или «защищенного» доступа, и даже «частный» легко взломать. Использование метода get создает свойство только для чтения, а instance_eval означает, что вы должны знать имя переменной экземпляра, поэтому значение будет аналогичным.

4 голосов
/ 02 декабря 2009

Если вы не используете опцию instance_eval (как опубликовано @jleedev) и решили использовать метод getter, вы все равно можете сохранить его protected

Если вам нужен метод protected в Ruby, просто сделайте следующее, чтобы создать метод получения, который может быть прочитан только из объектов того же класса:

class X
    def new()
        @a = 1
    end
    def m( other ) 
        @a == other.a
    end

    protected
    def a 
      @a
    end
end

x = X.new()
y = X.new()
x.m( y ) # Returns true
x.a      # Throws error
0 голосов
/ 02 декабря 2009

Не уверен, но это может помочь:

Вне класса это немного сложнее:

# Doesn't work:
irb -> a.@foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
        from (irb):9

# But you can access it this way:
irb -> a.instance_variable_get(:@foo)
    => []

http://whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility

...