Por que usar o attr_accessor, attr_reader e attr_writer do Ruby?

517

O Ruby tem essa maneira prática e conveniente de compartilhar variáveis ​​de instância usando chaves como

attr_accessor :var
attr_reader :var
attr_writer :var

Por que eu escolheria attr_readerou attr_writerse poderia simplesmente usar attr_accessor? Existe algo como desempenho (do qual duvido)? Eu acho que há uma razão, caso contrário eles não teriam feito essas chaves.

Voldemort
fonte
1
possível duplicata do O que é attr_accessor no Ruby?
sschuberth

Respostas:

746

Você pode usar os diferentes acessadores para comunicar sua intenção a alguém que está lendo seu código e facilitar a gravação de classes que funcionarão corretamente, independentemente de como a API pública seja chamada.

class Person
  attr_accessor :age
  ...
end

Aqui, vejo que posso ler e escrever a era.

class Person
  attr_reader :age
  ...
end

Aqui, vejo que só posso ler a idade. Imagine que ele é definido pelo construtor dessa classe e depois disso permanece constante. Se houvesse um mutador (escritor) para a idade e a classe fosse escrita assumindo que a idade, uma vez definida, não muda, um bug pode resultar do código que chama esse mutador.

Mas o que está acontecendo nos bastidores?

Se você escrever:

attr_writer :age

Isso é traduzido para:

def age=(value)
  @age = value
end

Se você escrever:

attr_reader :age

Isso é traduzido para:

def age
  @age
end

Se você escrever:

attr_accessor :age

Isso é traduzido para:

def age=(value)
  @age = value
end

def age
  @age
end

Sabendo disso, eis outra maneira de pensar sobre isso: Se você não tivesse os auxiliares de atributos e tivesse que escrever os acessadores por si mesmo, escreveria mais acessadores do que a sua classe precisava? Por exemplo, se a idade apenas precisasse ser lida, você também escreveria um método que permitisse que ela fosse escrita?

Wayne Conrad
fonte
53
Há também uma significativa vantagem de desempenho para escrever attr_reader :avs. def a; return a; end confreaks.net/videos/...
Nitrodist
83
@ Nitrodista, interessante. Para o Ruby 1.8.7, o attr_readeracessador definido leva 86% do tempo que o acessador definido manualmente. Para o Ruby 1.9.0, o attr_readeracessador definido leva 94% do tempo que o acessador definido manualmente. Em todos os meus testes, no entanto, os acessadores são rápidos: um acessador leva cerca de 820 nanossegundos (Ruby 1.8.7) ou 440 nanossegundos (Ruby 1.9). Nessas velocidades, você precisará ligar para um acessador centenas de milhões de vezes para obter o benefício de desempenho attr_accessorpara melhorar o tempo de execução geral em até um segundo.
Wayne Conrad
22
"Presumivelmente, é definido pelo construtor desta classe e permanece constante." Isso não é exato. Variáveis ​​de instância com leitores podem mudar com freqüência. No entanto, pretende-se que seus valores sejam alterados apenas em particular pela classe.
precisa saber é o seguinte
11
Você pode usar "," para adicionar mais de 2 atributos, como:attr_accessor :a, :b
Andrew_1510
2
pelo que vale a pena depois de todos esses anos: github.com/JuanitoFatas/… de acordo com os últimos benchmarks do ruby ​​2.2.0 attr_ * são mais rápidos que getters e setters.
Novinho
25

Todas as respostas acima estão corretas; attr_readere attr_writeré mais conveniente escrever do que digitar manualmente os métodos para os quais são abreviados. Além disso, eles oferecem desempenho muito melhor do que você mesmo escrevendo a definição do método. Para mais informações, consulte o slide 152 em diante desta palestra ( PDF ) de Aaron Patterson.

hawx
fonte
16

Nem todos os atributos de um objeto devem ser definidos diretamente de fora da classe. Ter gravadores para todas as variáveis ​​de instância é geralmente um sinal de encapsulamento fraco e um aviso de que você está introduzindo muito acoplamento entre suas classes.

Como exemplo prático: escrevi um programa de design em que você coloca itens dentro de contêineres. O item tinha attr_reader :container, mas não fazia sentido oferecer a um escritor, pois o único momento em que o contêiner do item deve mudar é quando ele é colocado em um novo, o que também requer informações de posicionamento.

Mandril
fonte
16

É importante entender que os acessadores restringem o acesso à variável, mas não ao conteúdo. Em ruby, como em outras linguagens OO, toda variável é um ponteiro para uma instância. Portanto, se você possui um atributo para um Hash, por exemplo, e o define como "somente leitura", sempre pode alterar o conteúdo, mas não o conteúdo do ponteiro. Veja isso:

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

Como você pode ver, é possível excluir um par de chave / valor do Hash @a, como adicionar novas chaves, alterar valores, etc. Mas você não pode apontar para um novo objeto porque é uma variável de instância somente leitura.

Korsmakolnikov
fonte
13

Você nem sempre deseja que suas variáveis ​​de instância sejam totalmente acessíveis de fora da classe. Existem muitos casos em que permitir o acesso de leitura a uma variável de instância faz sentido, mas a gravação pode não ser (por exemplo, um modelo que recupera dados de uma fonte somente leitura). Há casos em que você deseja o contrário, mas não consigo pensar em nenhum que não seja inventado do alto da minha cabeça.

coreyward
fonte