Pourquoi utiliser attr_accessor, attr_reader et attr_writer de Ruby?

517

Ruby a ce moyen pratique et pratique de partager des variables d'instance en utilisant des clés comme

attr_accessor :var
attr_reader :var
attr_writer :var

Pourquoi devrais-je choisir attr_readerou attr_writersi je pouvais simplement utiliser attr_accessor? Y a-t-il quelque chose comme la performance (dont je doute)? Je suppose qu'il y a une raison, sinon ils n'auraient pas fait de telles clés.

Voldemort
la source
1
doublon possible de Qu'est-ce que attr_accessor dans Ruby?
sschuberth

Réponses:

746

Vous pouvez utiliser les différents accesseurs pour communiquer votre intention à quelqu'un qui lit votre code et faciliter l'écriture de classes qui fonctionneront correctement quel que soit le nom de leur API publique.

class Person
  attr_accessor :age
  ...
end

Ici, je peux voir que je peux lire et écrire l'âge.

class Person
  attr_reader :age
  ...
end

Ici, je peux voir que je ne peux lire que l'âge. Imaginez qu'il soit défini par le constructeur de cette classe et qu'ensuite il reste constant. S'il y avait un mutateur (écrivain) pour l'âge et que la classe était écrite en supposant que l'âge, une fois défini, ne change pas, un bogue pourrait résulter du code appelant ce mutateur.

Mais que se passe-t-il en coulisses?

Si vous écrivez:

attr_writer :age

Cela se traduit par:

def age=(value)
  @age = value
end

Si vous écrivez:

attr_reader :age

Cela se traduit par:

def age
  @age
end

Si vous écrivez:

attr_accessor :age

Cela se traduit par:

def age=(value)
  @age = value
end

def age
  @age
end

Sachant cela, voici une autre façon de penser: si vous n'aviez pas les assistants attr _... et deviez écrire les accesseurs vous-même, écririez-vous plus d'accesseurs que votre classe n'en avait besoin? Par exemple, si l'âge devait seulement être lu, écririez-vous également une méthode permettant de l'écrire?

Wayne Conrad
la source
53
L' écriture présente également un avantage significatif en termes de performances par attr_reader :arapport à def a; return a; end confreaks.net/videos/…
Nitrodist
83
@Nitrodist, intéressant. Pour Ruby 1.8.7, l' attr_readeraccesseur défini prend 86% du temps que l'accesseur défini manuellement. Pour Ruby 1.9.0, l' attr_readeraccesseur défini prend 94% du temps que l'accesseur défini manuellement. Dans tous mes tests, cependant, les accesseurs sont rapides: un accesseur prend environ 820 nanosecondes (Ruby 1.8.7) ou 440 nanosecondes (Ruby 1.9). À ces vitesses, vous devrez appeler un accesseur des centaines de millions de fois pour que les performances attr_accessoraméliorent l'exécution globale d'une seconde.
Wayne Conrad
22
"Vraisemblablement, il est fixé par le constructeur de cette classe et reste constant." Ce n'est pas exact. Les variables d'instance avec des lecteurs peuvent changer fréquemment. Cependant, il est prévu que leurs valeurs ne soient modifiées qu'en privé par la classe.
mlibby
11
Vous pouvez utiliser "," pour ajouter plus de 2 attributs, tels que:attr_accessor :a, :b
Andrew_1510
2
pour ce qui vaut après toutes ces années: github.com/JuanitoFatas/… selon les derniers benchmarks sur ruby ​​2.2.0 attr_ * sont plus rapides que les getters et setters.
molli
25

Toutes les réponses ci-dessus sont correctes; attr_readeret attr_writersont plus pratiques à écrire qu’à taper manuellement les méthodes pour lesquelles ils sont des raccourcis. En dehors de cela, ils offrent des performances bien meilleures que l'écriture de la définition de la méthode vous-même. Pour plus d'informations, voir la diapositive 152 à partir de cette présentation ( PDF ) d'Aaron Patterson.

hawx
la source
16

Tous les attributs d'un objet ne sont pas censés être définis directement depuis l'extérieur de la classe. Avoir des rédacteurs pour toutes vos variables d'instance est généralement un signe de faible encapsulation et un avertissement que vous introduisez trop de couplage entre vos classes.

À titre d'exemple pratique: j'ai écrit un programme de conception où vous placez des articles dans des conteneurs. L'article avait attr_reader :container, mais cela n'avait aucun sens d'offrir un écrivain, car la seule fois où le conteneur de l'article devrait changer, c'est quand il est placé dans un nouveau, ce qui nécessite également des informations de positionnement.

Mandrin
la source
16

Il est important de comprendre que les accesseurs restreignent l'accès aux variables, mais pas à leur contenu. En ruby, comme dans certains autres langages OO, chaque variable est un pointeur vers une instance. Ainsi, si vous avez un attribut à un hachage, par exemple, et que vous le définissez en "lecture seule", vous pouvez toujours changer son contenu, mais pas le contenu du pointeur. Regarde ça:

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

Comme vous pouvez le voir, il est possible de supprimer une paire clé / valeur de Hash @a, comme ajouter de nouvelles clés, modifier des valeurs, eccetera. Mais vous ne pouvez pas pointer vers un nouvel objet car il s'agit d'une variable d'instance en lecture seule.

Korsmakolnikov
la source
13

Vous ne voulez pas toujours que vos variables d'instance soient entièrement accessibles depuis l'extérieur de la classe. Il y a de nombreux cas où autoriser l'accès en lecture à une variable d'instance est logique, mais il peut ne pas y écrire (par exemple, un modèle qui récupère des données à partir d'une source en lecture seule). Il y a des cas où vous voulez le contraire, mais je ne peux penser à aucun qui ne soit artificiel du haut de ma tête.

coreyward
la source