web-dev-qa-db-fra.com

Pourquoi utiliser attryaccessor, attr_reader et attr_writer de Ruby?

Ruby dispose de ce moyen pratique et pratique pour partager des variables d’instance en utilisant des clés telles que

attr_accessor :var
attr_reader :var
attr_writer :var

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

501
Voldemort

Vous pouvez utiliser les différents accesseurs pour communiquer votre intention à une personne lisant votre code et faciliter la rédaction de classes qui fonctionneront correctement quelle que soit la méthode utilisée pour appeler leur API publique.

class Person
  attr_accessor :age
  ...
end

Ici, je peux voir que je peux à la fois lire et écrire l’âge.

class Person
  attr_reader :age
  ...
end

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

Mais que se passe-t-il dans les coulisses?

Si vous écrivez:

attr_writer :age

Cela se traduit en:

def age=(value)
  @age = value
end

Si vous écrivez:

attr_reader :age

Cela se traduit en:

def age
  @age
end

Si vous écrivez:

attr_accessor :age

Cela se traduit en:

def age=(value)
  @age = value
end

def age
  @age
end

Sachant cela, voici une autre façon d’y réfléchir: si vous n’aviez pas les aides attr _..., et si vous deviez écrire vous-même les accesseurs, écririez-vous plus d’accesseurs que ce dont votre classe a besoin? Par exemple, si seulement l'âge devait être lu, écririez-vous également une méthode permettant de l'écrire?

732
Wayne Conrad

Toutes les réponses ci-dessus sont correctes. attr_reader et attr_writer sont plus pratiques à écrire que de saisir manuellement les méthodes pour lesquelles ils constituent un raccourci. En dehors de cela, ils offrent de bien meilleures performances que la définition de la méthode par vous-même. Pour plus d'informations, voir la diapositive 152 à partir de this talk ( PDF ) d'Aaron Patterson.

23
hawx

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

Comme exemple pratique: j'ai écrit un programme de conception dans lequel vous insérez des éléments dans des conteneurs. L'objet contenait attr_reader :container, mais il n'était pas logique de proposer un rédacteur, car le conteneur ne doit changer que lorsqu'il est placé dans un nouveau, ce qui nécessite également des informations de positionnement.

15
Chuck

Vous ne voulez pas toujours que vos variables d'instance soient entièrement accessibles de l'extérieur de la classe. Il existe de nombreux cas dans lesquels autoriser l'accès en lecture à une variable d'instance est logique, mais ne pas y écrire (par exemple, un modèle qui extrait des données d'une source en lecture seule). Il y a des cas où vous voulez le contraire, mais je ne peux penser à aucun de ceux qui ne sont pas artificiels de prime abord.

13
coreyward

Il est important de comprendre que les accesseurs limitent l'accès aux variables, mais pas à leur contenu. En Ruby, comme dans d’autres OO langues, chaque variable est un pointeur sur une instance. Ainsi, si vous avez un attribut à un hachage, par exemple, et que vous le définissez en "lecture seule", vous pouvez toujours en modifier le 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 constater, il est possible de supprimer une paire clé/valeur du hachage @a, en ajoutant de nouvelles clés, en modifiant les valeurs, etc. Mais vous ne pouvez pas pointer sur un nouvel objet car il s'agit d'une variable d'instance en lecture seule.

11
Korsmakolnikov