Можно ли сравнивать частные атрибуты в Ruby?

Я думаю в:

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

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


person OscarRyz    schedule 02.12.2009    source источник
comment
Короткий ответ: вам не нужен @ при вызове переменной экземпляра. Так что other.a — это все, что вам нужно.   -  person Chuck Vose    schedule 02.12.2009
comment
@Chuck Vose: я думаю, не все ответы должны быть правильными. Я написал @a == other.a, и переводчик сказал: NoMethodError: undefined method a' для #‹X:0x1011bbcd0 @a=1›`   -  person OscarRyz    schedule 02.12.2009
comment
Ruby не имеет атрибутов как таковых. @a — это переменная экземпляра.   -  person Andrew Grimm    schedule 28.03.2012


Ответы (4)


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

Геттер:

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 есть понятие «друг» или «защищенный» доступ, и даже «частный» легко взломать. Использование геттера создает свойство, доступное только для чтения, а instance_eval означает, что вы должны знать имя переменной экземпляра, поэтому коннотация аналогична.

person Josh Lee    schedule 02.12.2009
comment
Очень здорово узнать о instance_eval. Сначала я тебе не поверил, поэтому попробовал. Работал как шарм. Мне нужно часто возвращаться к программированию на Ruby... это действительно классный язык. - person Doug Neiner; 02.12.2009
comment
Однако у него есть protected методы. Я обновил свой ответ, чтобы показать это. - person Doug Neiner; 02.12.2009
comment
О, protected. Это, вероятно, лучший способ, так как синтаксис более естественный. - person Josh Lee; 02.12.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, если на самом деле я вызываю метод класса с именем 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
comment
Это потрясающий ответ. Я давно не видел, чтобы кто-то прилагал столько усилий к вопросу. Спасибо! - person Chuck Vose; 02.12.2009
comment
... вы, вероятно, спрашиваете себя: почему я должен переопределять метод экземпляра с именем initialize, когда я на самом деле вызываю метод класса с именем new? . . . ха ха .. naahh Я никогда не спрашивал себя об этом. Я подумал, что О, так это initialize тогда. Спасибо за ответ, читаю каждые два дня (пока не получу) ;) - person OscarRyz; 05.12.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
person Doug Neiner    schedule 02.12.2009
comment
Обратите внимание, что здесь есть условие гонки: сначала attr_reader создается общедоступным, затем, в качестве отдельного шага, делается защищенным. Другой поток может вызвать метод доступа между этими двумя шагами. В моем редактировании не было этого состояния гонки, потому что оно переключилось на защищенный сначала, а затем создало метод доступа. В любом случае, это редактирование все же лучше, чем исходная авторская версия, которая даже не была допустимым синтаксисом Ruby. - person Jörg W Mittag; 04.12.2009
comment
@Jorg, спасибо, что заметили проблему с двоеточием, но, пожалуйста, не переписывайте мой код. Также согласовано условие гонки с другим редактором. Я также откатил его изменения. - person Doug Neiner; 04.12.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

person SeanJA    schedule 02.12.2009