Perché usare attr_accessor, attr_reader e attr_writer di Ruby?


517

Ruby ha questo modo pratico e conveniente per condividere variabili di istanza usando chiavi come

attr_accessor :var
attr_reader :var
attr_writer :var

Perché dovrei scegliere attr_readero attr_writerse potessi semplicemente usare attr_accessor? C'è qualcosa come la performance (di cui dubito)? Immagino che ci sia una ragione, altrimenti non avrebbero creato tali chiavi.


Risposte:


746

È possibile utilizzare i diversi accessori per comunicare le proprie intenzioni a qualcuno che legge il proprio codice e semplificare la scrittura di classi che funzioneranno correttamente, indipendentemente da come viene chiamata la loro API pubblica.

class Person
  attr_accessor :age
  ...
end

Qui, posso vedere che posso sia leggere che scrivere l'età.

class Person
  attr_reader :age
  ...
end

Qui, posso vedere che posso solo leggere l'età. Immagina che sia impostato dal costruttore di questa classe e che successivamente rimanga costante. Se ci fosse un mutatore (scrittore) per età e la classe fosse scritta supponendo che l'età, una volta impostata, non cambi, allora potrebbe derivare un errore dal codice che chiama quel mutatore.

Ma cosa sta succedendo dietro le quinte?

Se scrivi:

attr_writer :age

Questo viene tradotto in:

def age=(value)
  @age = value
end

Se scrivi:

attr_reader :age

Questo viene tradotto in:

def age
  @age
end

Se scrivi:

attr_accessor :age

Questo viene tradotto in:

def age=(value)
  @age = value
end

def age
  @age
end

Sapendo questo, ecco un altro modo di pensarci: se non avessi gli helper attr _... e dovessi scrivere tu stesso gli accessor, scriveresti più accessori di quelli necessari alla tua classe? Ad esempio, se l'età avesse solo bisogno di essere letta, scriveresti anche un metodo che consente di scriverlo?


53
C'è anche un notevole vantaggio in termini di prestazioni nella scrittura attr_reader :arispetto a def a; return a; end confreaks.net/videos/…
Nitrodist

83
@Nitrodist, Interessante. Per Ruby 1.8.7, l' attr_readeraccessor definito impiega l'86% delle volte dell'accessor definito manualmente. Per Ruby 1.9.0, l' attr_readeraccessorio definito impiega il 94% delle volte dell'accessorio definito manualmente. In tutti i miei test, tuttavia, gli accessor sono veloci: un accessor richiede circa 820 nanosecondi (Ruby 1.8.7) o 440 nanosecondi (Ruby 1.9). A quelle velocità, dovrai chiamare un accessor centinaia di milioni di volte per il vantaggio in termini di prestazioni di attr_accessormigliorare il tempo di esecuzione complessivo anche di un secondo.
Wayne Conrad,

22
"Presumibilmente, è impostato dal costruttore di questa classe e rimane costante." Questo non è accurato. Le variabili di istanza con i lettori potrebbero cambiare frequentemente. Tuttavia, si intende che i loro valori vengano modificati solo privatamente dalla classe.
mlibby,

11
Puoi utilizzare "," per aggiungere più di 2 attributi, come ad esempio:attr_accessor :a, :b
Andrew_1510

2
per quello che vale dopo tutti questi anni: github.com/JuanitoFatas/… secondo gli ultimi benchmark su ruby ​​2.2.0 attr_ * sono più veloci di getter e setter.
Molli,

25

Tutte le risposte sopra sono corrette; attr_readere attr_writersono più convenienti da scrivere rispetto alla digitazione manuale dei metodi per cui sono abbreviati. Oltre a ciò offrono prestazioni molto migliori rispetto alla scrittura della definizione del metodo da soli. Per ulteriori informazioni, vedere la diapositiva 152 in avanti di questo discorso ( PDF ) di Aaron Patterson.


16

Non tutti gli attributi di un oggetto devono essere impostati direttamente dall'esterno della classe. Avere scrittori per tutte le variabili di istanza è generalmente un segno di incapsulamento debole e un avvertimento che stai introducendo un eccessivo accoppiamento tra le tue classi.

Come esempio pratico: ho scritto un programma di progettazione in cui si inseriscono oggetti all'interno di contenitori. L'oggetto aveva attr_reader :container, ma non aveva senso offrire uno scrittore, poiché l'unica volta in cui il contenitore dell'articolo doveva cambiare è quando viene inserito in uno nuovo, il che richiede anche informazioni sul posizionamento.


16

È importante comprendere che gli accessor limitano l'accesso alla variabile, ma non al loro contenuto. In ruby, come in alcune altre lingue OO, ogni variabile è un puntatore a un'istanza. Quindi, se ad esempio hai un attributo a un hash e lo imposti in "sola lettura", puoi sempre modificarne il contenuto, ma non quello del puntatore. Guarda questo:

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>'

Come puoi vedere è possibile eliminare una coppia chiave / valore da Hash @a, come aggiungere nuove chiavi, modificare valori, eccetera. Ma non puoi puntare a un nuovo oggetto perché è una variabile di istanza di sola lettura.


13

Non si desidera sempre che le variabili dell'istanza siano completamente accessibili dall'esterno della classe. Esistono molti casi in cui è logico consentire l'accesso in lettura a una variabile di istanza, ma la scrittura su di essa potrebbe non esserlo (ad esempio un modello che recupera i dati da un'origine di sola lettura). Ci sono casi in cui vuoi il contrario, ma non riesco a pensare a quelli che non sono inventati dalla parte superiore della mia testa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.