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

517

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

attr_accessor :var
attr_reader :var
attr_writer :var

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

Волдеморт
источник
1
Возможный дубликат Что такое attr_accessor в Ruby?
sschuberth

Ответы:

746

Вы можете использовать различные средства доступа, чтобы сообщить о своем намерении кому-то, читающему ваш код, и упростить написание классов, которые будут работать правильно, независимо от того, как вызывается их публичный 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 _..., и вы должны были написать аксессоры самостоятельно, вы бы написали больше аксессоров, чем требовалось вашему классу? Например, если нужно только прочитать возраст, вы бы также написали метод, позволяющий записать его?

Уэйн Конрад
источник
53
Существует также значительное преимущество в производительности по attr_reader :aсравнению с def a; return a; end confreaks.net/videos/…
Nitrodist
83
@Nitrodist, интересно. Для Ruby 1.8.7 attr_readerопределенный определитель доступа занимает 86% времени, которое делает средство доступа, определенное вручную. Для Ruby 1.9.0 attr_readerопределенный аксессор занимает 94% времени, определенного вручную. Однако во всех моих тестах средства доступа работают быстро: средство доступа занимает около 820 наносекунд (Ruby 1.8.7) или 440 наносекунд (Ruby 1.9). На этих скоростях вам нужно будет вызывать метод доступа сотни миллионов раз, attr_accessorчтобы повысить производительность и повысить общее время выполнения даже на одну секунду.
Уэйн Конрад
22
«Предположительно, он устанавливается конструктором этого класса и остается постоянным». Это не точно. Переменные экземпляра с читателями могут часто меняться. Однако предполагается, что их значения могут быть изменены только в частном порядке классом.
mlibby
11
Вы можете использовать ",", чтобы добавить более 2 атрибутов, таких как:attr_accessor :a, :b
Andrew_1510
2
за что стоит после всех этих лет: github.com/JuanitoFatas/… в соответствии с последними тестами на ruby ​​2.2.0 attr_ * быстрее, чем геттеры и сеттеры.
Молли
25

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

HAWX
источник
16

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

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

цыпленок
источник
16

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

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, добавить новые ключи, изменить значения и так далее. Но вы не можете указать на новый объект, потому что это переменная экземпляра только для чтения.

Korsmakolnikov
источник
13

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

coreyward
источник