Ruby attr_reader позволяет изменять строковую переменную при использовании <<

Наткнулся на какое-то странное поведение и подумал, может ли кто-нибудь еще подтвердить то, что я вижу.

Предположим, вы создаете класс с переменной-членом и позволяете читать его с помощью attr_reader.

class TestClass
  attr_reader :val

  def initialize(value)
   @val = value
  end
end

Теперь, когда я делаю следующее, кажется, что оно изменяет значение @val, хотя я только предоставил ему права на чтение.

test = TestClass.new('hello')
puts test.val
test.val << ' world'
puts test.val

Это возвращает

hello
hello world

Это просто результат некоторого тестирования, которое я провел в irb, поэтому не уверен, что это всегда так

4 ответа

Решение

Вы на самом деле не пишете атрибут val. Вы читаете его и вызываете для него метод (метод "<<").

Если вам нужен метод доступа, который предотвращает описанную вами модификацию, вы можете захотеть реализовать метод, который возвращает копию @val, вместо использования attr_reader.

Небольшая модификация вашего примера:

test = TestClass.new([])

Теперь вы должны получить (замените put на p, чтобы получить внутреннее представление):

[]
['hello']

Это то же самое. Вы "читаете" val, и теперь вы можете что-то с этим сделать. В моем примере вы добавляете что-то в массив, в своем примере вы добавляете что-то в свою строку.

Доступ для чтения читает объект (который может быть изменен), доступ для записи изменяет атрибут (он заменяет его).

Возможно, вы ищете freeze:

class TestClass
  attr_reader :val

  def initialize(value)
   @val = value
   @val.freeze
  end
end

test = TestClass.new('hello')
puts test.val
test.val << ' world'
puts test.val

Это заканчивается в:

__temp.rb:12:in `<main>': can't modify frozen string (RuntimeError)
hello

Назначение отличается от модификации, а переменные отличаются от объектов.

test.val = "hello world"

будет случай присвоения @val переменная экземпляра (которая не будет работать), тогда как

test.val << " world"

будет модификация объекта, на который ссылается @val,

Почему отсутствие оператора присваивания позволяет мне изменять константу Ruby без предупреждения компилятора? это похожий вопрос, но речь идет о константах, а не переменных экземпляра.

Хотя это кажется неожиданным, это совершенно верно. Позволь мне объяснить.

attr_reader а также attr_writer методы макроса класса определяют методы "getter" и "setter" для переменных экземпляра.

Без метода "getter" у вас нет доступа к переменным экземпляра объекта просто потому, что вы не находитесь в контексте этого объекта. Метод "setter" по сути такой:

def variable=(value)
  @variable = value
end

Поскольку переменная экземпляра указывает на изменяемый объект с самим набором методов, если вы "получаете" его и манипулируете им, само собой разумеется, что эти изменения будут приняты. Вам не нужно использовать вышеуказанный метод установки для вызова variable.<<(value),

Другие вопросы по тегам