Навіщо використовувати attr_accessor, attr_reader та attr_writer Ruby?


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
Ви можете використовувати ",", щоб додати більше двох атрибутів, таких як:attr_accessor :a, :b
Andrew_1510

2
для чого варто після всіх цих років: github.com/JuanitoFatas/… згідно з останніми орієнтирами на рубіні 2.2.0 attr_ * швидше, ніж геттери та сетери.
моллі

25

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


16

Не всі атрибути об'єкта мають бути встановлені безпосередньо поза класом. Наявність авторів для всіх змінних ваших примірників, як правило, є ознакою слабкої інкапсуляції та попередження про те, що ви вводите занадто багато зв'язку між своїми класами.

Як практичний приклад: я написав програму дизайну, де ви поміщаєте предмети всередину контейнерів. Елемент мав attr_reader :container, але пропонувати автору не було сенсу, оскільки єдиний раз, коли контейнер повинен змінюватися, це його розміщення в новому, що також вимагає інформації про позиціонування.


16

Важливо розуміти, що аксесуари обмежують доступ до змінної, але не їх змісту. У рубіні, як і в деяких інших мовах ОО, кожна змінна є вказівником на екземпляр. Отже, якщо у вас є, наприклад, атрибут 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, оскільки додайте нові ключі, змініть значення, eccetera. Але ви не можете вказати на новий об’єкт, оскільки це змінна інстанція лише для читання.


13

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.