¿Por qué usar attr_accessor, attr_reader y attr_writer de Ruby?

517

Ruby tiene esta forma práctica y conveniente de compartir variables de instancia mediante el uso de claves como

attr_accessor :var
attr_reader :var
attr_writer :var

¿Por qué elegiría attr_readero attr_writersi simplemente podría usar attr_accessor? ¿Hay algo como el rendimiento (que dudo)? Supongo que hay una razón, de lo contrario no habrían hecho tales llaves.

Voldemort
fuente
1
posible duplicado de ¿Qué es attr_accessor en Ruby?
sschuberth

Respuestas:

746

Puede usar los diferentes accesores para comunicar su intención a alguien que lea su código y facilitar la escritura de clases que funcionarán correctamente sin importar cómo se llame su API pública.

class Person
  attr_accessor :age
  ...
end

Aquí, puedo ver que puedo leer y escribir la edad.

class Person
  attr_reader :age
  ...
end

Aquí, puedo ver que solo puedo leer la edad. Imagine que lo establece el constructor de esta clase y luego se mantiene constante. Si hubo un mutador (escritor) para la edad y la clase se escribió asumiendo que esa edad, una vez establecida, no cambia, entonces podría producirse un error al llamar al mutador al código.

Pero, ¿qué está pasando detrás de escena?

Si tú escribes:

attr_writer :age

Eso se traduce en:

def age=(value)
  @age = value
end

Si tú escribes:

attr_reader :age

Eso se traduce en:

def age
  @age
end

Si tú escribes:

attr_accessor :age

Eso se traduce en:

def age=(value)
  @age = value
end

def age
  @age
end

Sabiendo eso, aquí hay otra forma de pensarlo: si no tuviera los ayudantes attr _... y tuviera que escribir los accesos usted mismo, ¿escribiría más accesos de los que su clase necesitaba? Por ejemplo, si la edad solo necesita ser leída, ¿también escribirías un método que permita que se escriba?

Wayne Conrad
fuente
53
También hay una ventaja significativa en el rendimiento de la escritura attr_reader :afrente a def a; return a; end confreaks.net/videos/…
Nitrodist
83
@Nitrodist, Interesante. Para Ruby 1.8.7, el attr_readeraccesorio definido toma el 86% del tiempo que lo hace el accesorio definido manualmente. Para Ruby 1.9.0, el descriptor de attr_readeracceso definido toma el 94% del tiempo que el descriptor de acceso definido manualmente. Sin embargo, en todas mis pruebas, los accesores son rápidos: un accesor tarda aproximadamente 820 nanosegundos (Ruby 1.8.7) o 440 nanosegundos (Ruby 1.9). A esas velocidades, necesitará llamar a un accesorio cientos de millones de veces para obtener el beneficio de rendimiento de attr_accessormejorar el tiempo de ejecución general en incluso un segundo.
Wayne Conrad
22
"Presumiblemente, lo establece el constructor de esta clase y permanece constante". Eso no es exacto. Las variables de instancia con lectores pueden cambiar con frecuencia. Sin embargo, se pretende que sus valores solo sean modificados de forma privada por la clase.
mlibby
11
Puede usar "," para agregar más de 2 atributos, como:attr_accessor :a, :b
Andrew_1510
2
para lo que vale después de todos estos años: github.com/JuanitoFatas/… según los últimos puntos de referencia en ruby ​​2.2.0 attr_ * son más rápidos que los captadores y setters.
molli
25

Todas las respuestas anteriores son correctas; attr_readery attr_writerson más convenientes para escribir que escribir manualmente los métodos para los que son shorthands. Aparte de eso, ofrecen un rendimiento mucho mejor que escribir la definición del método usted mismo. Para obtener más información, consulte la diapositiva 152 en adelante de esta charla ( PDF ) de Aaron Patterson.

halcón
fuente
16

No todos los atributos de un objeto deben establecerse directamente desde fuera de la clase. Tener escritores para todas sus variables de instancia es generalmente un signo de encapsulación débil y una advertencia de que está introduciendo demasiado acoplamiento entre sus clases.

Como ejemplo práctico: escribí un programa de diseño en el que pones artículos dentro de contenedores. El artículo tenía attr_reader :container, pero no tenía sentido ofrecer un escritor, ya que la única vez que el contenedor del artículo debería cambiar es cuando se coloca en uno nuevo, que también requiere información de posicionamiento.

Arrojar
fuente
16

Es importante comprender que los accesores restringen el acceso a variables, pero no a su contenido. En ruby, como en algunos otros lenguajes OO, cada variable es un puntero a una instancia. Entonces, si tiene un atributo para un Hash, por ejemplo, y lo configura como "solo lectura", siempre puede cambiar su contenido, pero no el contenido del puntero. Mira esto:

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 puede ver, es posible eliminar un par clave / valor del Hash @a, como agregar nuevas claves, cambiar valores, etc. Pero no puede apuntar a un nuevo objeto porque es una variable de instancia de solo lectura.

Korsmakolnikov
fuente
13

No siempre desea que sus variables de instancia sean totalmente accesibles desde fuera de la clase. Hay muchos casos en los que tiene sentido permitir el acceso de lectura a una variable de instancia, pero no es posible escribir en ella (por ejemplo, un modelo que recupera datos de una fuente de solo lectura). Hay casos en los que quieres lo contrario, pero no puedo pensar en ninguno que no esté ideado fuera de mi cabeza.

coreyward
fuente