Warum Rubys attr_accessor, attr_reader und attr_writer verwenden?

517

Ruby bietet diese praktische und bequeme Möglichkeit, Instanzvariablen mithilfe von Schlüsseln wie freizugeben

attr_accessor :var
attr_reader :var
attr_writer :var

Warum sollte ich wählen attr_readeroder attr_writerwenn ich einfach verwenden könnte attr_accessor? Gibt es so etwas wie Leistung (was ich bezweifle)? Ich denke, es gibt einen Grund, sonst hätten sie solche Schlüssel nicht gemacht.

Voldemort
quelle
1
Mögliches Duplikat von Was ist attr_accessor in Ruby?
Sschuberth

Antworten:

746

Sie können die verschiedenen Zugriffsmethoden verwenden, um jemandem, der Ihren Code liest, Ihre Absicht mitzuteilen und das Schreiben von Klassen zu vereinfachen, die unabhängig vom Aufruf ihrer öffentlichen API ordnungsgemäß funktionieren.

class Person
  attr_accessor :age
  ...
end

Hier kann ich sehen, dass ich das Alter sowohl lesen als auch schreiben kann.

class Person
  attr_reader :age
  ...
end

Hier kann ich sehen, dass ich nur das Alter lesen darf. Stellen Sie sich vor, dass es vom Konstruktor dieser Klasse festgelegt wird und danach konstant bleibt. Wenn es einen Mutator (Writer) für das Alter gibt und die Klasse unter der Annahme geschrieben wird, dass sich das einmal festgelegte Alter nicht ändert, kann ein Fehler durch den Code entstehen, der diesen Mutator aufruft.

Aber was passiert hinter den Kulissen?

Wenn Sie schreiben:

attr_writer :age

Das wird übersetzt in:

def age=(value)
  @age = value
end

Wenn Sie schreiben:

attr_reader :age

Das wird übersetzt in:

def age
  @age
end

Wenn Sie schreiben:

attr_accessor :age

Das wird übersetzt in:

def age=(value)
  @age = value
end

def age
  @age
end

Wenn Sie das wissen, können Sie darüber nachdenken: Wenn Sie nicht die attr _...-Helfer hätten und die Accessoren selbst schreiben müssten, würden Sie dann mehr Accessoren schreiben, als Ihre Klasse benötigt? Wenn beispielsweise das Alter nur gelesen werden müsste, würden Sie dann auch eine Methode schreiben, mit der es geschrieben werden kann?

Wayne Conrad
quelle
53
Es gibt auch einen signifikanten Leistungsvorteil beim Schreiben attr_reader :agegenüber def a; return a; end confreaks.net/videos/…
Nitrodist
83
@ Nitrodist, interessant. Für Ruby 1.8.7 benötigt der attr_readerdefinierte Accessor 86% der Zeit, die der manuell definierte Accessor benötigt. Für Ruby 1.9.0 benötigt der attr_readerdefinierte Accessor 94% der Zeit, die der manuell definierte Accessor benötigt. In allen meinen Tests sind Accessoren jedoch schnell: Ein Accessor benötigt ungefähr 820 Nanosekunden (Ruby 1.8.7) oder 440 Nanosekunden (Ruby 1.9). Bei diesen Geschwindigkeiten müssen Sie einen Accessor hunderte Millionen Mal anrufen, um die Leistung attr_accessorzu verbessern und die Gesamtlaufzeit um nur eine Sekunde zu verbessern.
Wayne Conrad
22
"Vermutlich wird es vom Konstruktor dieser Klasse festgelegt und bleibt konstant." Das ist nicht richtig. Instanzvariablen mit Lesern können sich häufig ändern. Es ist jedoch beabsichtigt, dass ihre Werte nur privat von der Klasse geändert werden.
mlibby
11
Sie können "," verwenden, um mehr als 2 Attribute hinzuzufügen, wie zum Beispiel:attr_accessor :a, :b
Andrew_1510
2
Für das, was nach all den Jahren wert ist: github.com/JuanitoFatas/… sind nach den neuesten Benchmarks für Ruby 2.2.0 attr_ * schneller als Getter und Setter.
Molli
25

Alle obigen Antworten sind richtig; attr_readerund attr_writersind bequemer zu schreiben, als die Methoden, für die sie Abkürzungen sind, manuell einzugeben. Abgesehen davon bieten sie eine viel bessere Leistung als das Schreiben der Methodendefinition selbst. Weitere Informationen finden Sie auf Folie 152 ab diesem Vortrag ( PDF ) von Aaron Patterson.

Hawx
quelle
16

Nicht alle Attribute eines Objekts sollen direkt von außerhalb der Klasse festgelegt werden. Autoren für alle Ihre Instanzvariablen zu haben, ist im Allgemeinen ein Zeichen für eine schwache Kapselung und eine Warnung, dass Sie zu viel Kopplung zwischen Ihren Klassen einführen.

Als praktisches Beispiel: Ich habe ein Designprogramm geschrieben, in dem Sie Gegenstände in Behälter legen. Der Artikel hatte attr_reader :container, aber es war nicht sinnvoll, einen Schreiber anzubieten, da sich der Container des Artikels nur ändern sollte, wenn er in einen neuen platziert wird, für den auch Positionsinformationen erforderlich sind.

Futter
quelle
16

Es ist wichtig zu verstehen, dass Accessoren den Zugriff auf Variablen einschränken, nicht jedoch auf deren Inhalt. In Ruby ist wie in einigen anderen OO-Sprachen jede Variable ein Zeiger auf eine Instanz. Wenn Sie beispielsweise ein Attribut für einen Hash haben und es auf "schreibgeschützt" setzen, können Sie dessen Inhalt immer ändern, nicht jedoch den Inhalt des Zeigers. Schau dir das an:

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

Wie Sie sehen, ist es möglich, ein Schlüssel / Wert-Paar aus dem Hash @a zu löschen, indem Sie neue Schlüssel hinzufügen, Werte ändern usw. Sie können jedoch nicht auf ein neues Objekt verweisen, da es sich um eine schreibgeschützte Instanzvariable handelt.

Korsmakolnikov
quelle
13

Sie möchten nicht immer, dass Ihre Instanzvariablen von außerhalb der Klasse vollständig zugänglich sind. Es gibt viele Fälle, in denen das Ermöglichen des Lesezugriffs auf eine Instanzvariable sinnvoll ist, das Schreiben jedoch möglicherweise nicht (z. B. ein Modell, das Daten aus einer schreibgeschützten Quelle abruft). Es gibt Fälle, in denen Sie das Gegenteil wollen, aber ich kann mir keine vorstellen, die mir nicht auf den Kopf gestellt wurden.

Coreyward
quelle