Зачем использовать Ruby attr_accessor, attr_reader и attr_writer?

Ruby имеет этот удобный и удобный способ обмена переменными экземпляра с помощью таких клавиш, как

attr_accessor :var
attr_reader :var
attr_writer :var

Почему бы мне выбрать attr_reader или attr_writer, если бы я мог просто использовать attr_accessor? Есть ли что-то вроде производительности (что я сомневаюсь)? Я думаю, есть причина, иначе они не сделали бы такие ключи.

Ответ 1

Вы можете использовать различные аксессоры, чтобы сообщить свое намерение кому-либо, читающему ваш код, и упростить запись классов, которые будут работать правильно независимо от того, как их публичный API будет вызван.

class Person
  attr_accessor :age
  ...
end

Здесь я вижу, что я могу читать и писать возраст.

class Person
  attr_reader :age
  ...
end

Здесь я вижу, что могу читать только возраст. Представьте, что он задан конструктором этого класса и после этого остается постоянным. Если бы существовал мутатор (писатель) по возрасту, и класс был написан, предполагая, что возраст, когда-то установленный, не изменяется, тогда ошибка может возникнуть из-за кода, вызывающего этот мутатор.

Но что происходит за кулисами?

Если вы пишете:

attr_writer :age

Это преобразуется в:

def age=(value)
  @age = value
end

Если вы пишете:

attr_reader :age

Это преобразуется в:

def age
  @age
end

Если вы пишете:

attr_accessor :age

Это преобразуется в:

def age=(value)
  @age = value
end

def age
  @age
end

Зная, что здесь еще один способ подумать об этом: Если у вас не было помощников attr _..., и вам пришлось писать аксессуры самостоятельно, вы могли бы написать больше аксессуаров, чем ваш класс? Например, если нужно было читать только возраст, вы также могли бы написать метод, позволяющий его записать?

Ответ 2

Все приведенные выше ответы верны; attr_reader и attr_writer удобнее писать, чем вручную набирать методы, для которых они предназначены. Кроме того, они предлагают гораздо лучшую производительность, чем самим описанием метода. Для получения дополнительной информации см. Слайд 152 от этого разговора (PDF) Аарона Паттерсона.

Ответ 3

Не все атрибуты объекта предназначены для непосредственного задания извне класса. Наличие писателей для всех ваших переменных экземпляра, как правило, является признаком слабой инкапсуляции и предупреждением о том, что вы вводите слишком много связей между вашими классами.

В качестве практического примера: я написал программу проектирования, в которой вы помещаете предметы внутри контейнеров. Элемент имел attr_reader :container, но не было смысла предлагать автора, поскольку единственный раз, когда контейнер элемента должен меняться, - это когда он помещается в новый, что также требует информации о местоположении.

Ответ 4

Вы не всегда хотите, чтобы ваши переменные экземпляра были полностью доступны извне класса. Существует множество случаев, когда разрешение доступа к переменной экземпляра имеет смысл, но запись на него может не быть (например, модель, которая извлекает данные из источника, доступного только для чтения). Бывают случаи, когда вы хотите иметь обратную сторону, но я не могу придумать ни одного, что не было бы уклонено от головы.

Ответ 5

Важно понимать, что аксессоры ограничивают доступ к переменной, но не их содержимому. В рубине, как и в некоторых других языках OO, каждая переменная является указателем на экземпляр. Поэтому, если у вас есть атрибут хэша, например, и вы установите его как "только для чтения", вы всегда можете изменить его содержимое, но не содержание указателя. Посмотрите на это:

irb(main):024:0> class A
irb(main):025:1> attr_reader :a
irb(main):026:1> def initialize
irb(main):027:2> @a = {a:1, b:2}
irb(main):028:2> end
irb(main):029:1> end
=> :initialize
irb(main):030:0> a = A.new
=> #<A:0x007ffc5a10fe88 @a={:a=>1, :b=>2}>
irb(main):031:0> a.a
=> {:a=>1, :b=>2}
irb(main):032:0> a.a.delete(:b)
=> 2
irb(main):033:0> a.a
=> {:a=>1}
irb(main):034:0> a.a = {}
NoMethodError: undefined method `a=' for #<A:0x007ffc5a10fe88 @a={:a=>1}>
        from (irb):34
        from /usr/local/bin/irb:11:in `<main>'

Как вы можете видеть, возможно удалить пару ключ/значение из Hash @a, как добавить новые ключи, изменить значения, eccetera. Но вы не можете указать на новый объект, потому что это переменная экземпляра только для чтения.