Dlaczego warto korzystać z Ruby's attr_accessor, attr_reader i attr_writer?

517

Ruby ma wygodny i wygodny sposób udostępniania zmiennych instancji za pomocą kluczy podobnych do

attr_accessor :var
attr_reader :var
attr_writer :var

Dlaczego miałbym wybrać, attr_readerczy attr_writermógłbym po prostu użyć attr_accessor? Czy istnieje coś takiego jak wydajność (w co wątpię)? Sądzę, że jest ku temu powód, inaczej nie zrobiliby takich kluczy.

Voldemort
źródło
1
możliwy duplikat Co to jest attr_accessor w Ruby?
sschuberth

Odpowiedzi:

746

Możesz użyć różnych akcesoriów, aby przekazać swoją intencję komuś, kto czyta Twój kod, i ułatwić pisanie klas, które będą działać poprawnie bez względu na to, jak wywoływany jest ich publiczny interfejs API.

class Person
  attr_accessor :age
  ...
end

Widzę tutaj, że mogę zarówno czytać, jak i pisać wiek.

class Person
  attr_reader :age
  ...
end

Widzę tutaj, że mogę tylko odczytać wiek. Wyobraź sobie, że jest ustawiony przez konstruktora tej klasy, a potem pozostaje stały. Gdyby istniał mutator (pisarz) dla wieku, a klasa została napisana przy założeniu, że ustawiony wiek nie zmienia się, wówczas błąd mógłby wynikać z wywołania kodu przez tego mutatora.

Ale co dzieje się za kulisami?

Jeśli napiszesz:

attr_writer :age

To przekłada się na:

def age=(value)
  @age = value
end

Jeśli napiszesz:

attr_reader :age

To przekłada się na:

def age
  @age
end

Jeśli napiszesz:

attr_accessor :age

To przekłada się na:

def age=(value)
  @age = value
end

def age
  @age
end

Wiedząc o tym, oto inny sposób, aby o tym pomyśleć: jeśli nie miałbyś pomocników ATTR _... i musiałbyś sam napisać akcesory, czy napisałbyś więcej akcesorów niż potrzebna jest klasa? Na przykład, jeśli wiek należy tylko odczytać, czy napisałbyś również metodę pozwalającą na jego zapisanie?

Wayne Conrad
źródło
53
Istnieje także znacząca przewaga wydajności nad pisaniem attr_reader :akontra def a; return a; end confreaks.net/videos/…
Nitrodist
83
@Nitrodist, ciekawe. W Ruby 1.8.7 attr_readerzdefiniowany moduł dostępu zajmuje 86% czasu, co robi ręcznie zdefiniowany moduł dostępu. W przypadku Ruby 1.9.0 attr_readerzdefiniowany moduł dostępu zajmuje 94% czasu niż ręcznie zdefiniowany moduł dostępu . Jednak we wszystkich moich testach akcesoria są szybkie: akcesorium zajmuje około 820 nanosekund (Ruby 1.8.7) lub 440 nanosekund (Ruby 1.9). Przy tych prędkościach musisz zadzwonić do akcesorium setki milionów razy, attr_accessoraby zwiększyć wydajność i poprawić ogólny czas działania nawet o jedną sekundę.
Wayne Conrad,
22
„Przypuszczalnie jest ustawiony przez konstruktora tej klasy i pozostaje niezmienny”. To nie jest dokładne. Zmienne instancji z czytnikami mogą się często zmieniać. Jednak zamierzone jest, aby ich wartości były zmieniane tylko prywatnie przez klasę.
mlibby,
11
Możesz użyć „,”, aby dodać więcej niż 2 atrybuty, takie jak:attr_accessor :a, :b
Andrew_1510,
2
za co warto po tylu latach: github.com/JuanitoFatas/... według najnowszych testów na Ruby 2.2.0 attr_ * są szybsze niż getters i setters.
molli
25

Wszystkie powyższe odpowiedzi są poprawne; attr_readeri attr_writersą wygodniejsze w pisaniu niż ręczne pisanie metod, dla których są skrótami. Poza tym oferują znacznie lepszą wydajność niż samodzielne pisanie definicji metody. Aby uzyskać więcej informacji, zobacz slajd 152 i następne z tej rozmowy ( PDF ) Aarona Pattersona.

jastrząb
źródło
16

Nie wszystkie atrybuty obiektu powinny być ustawiane bezpośrednio spoza klasy. Posiadanie programów piszących dla wszystkich zmiennych instancji jest zazwyczaj oznaką słabego enkapsulacji i ostrzeżeniem, że wprowadzasz zbyt wiele sprzężeń między klasami.

Jako praktyczny przykład: napisałem program do projektowania, w którym wkładasz przedmioty do pojemników. Element miał attr_reader :container, ale nie miał sensu oferować pisarza, ponieważ pojemnik na przedmiot powinien zmienić się tylko wtedy, gdy zostanie umieszczony w nowym, co również wymaga informacji o położeniu.

Głaskanie pod brodę
źródło
16

Ważne jest, aby zrozumieć, że akcesory ograniczają dostęp do zmiennych, ale nie ich treści. W Rubim, podobnie jak w niektórych innych językach OO, każda zmienna jest wskaźnikiem do instancji. Jeśli więc masz na przykład atrybut skrótu i ​​ustawisz go jako „tylko do odczytu”, zawsze możesz zmienić jego zawartość, ale nie zawartość wskaźnika. Spójrz na to:

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

Jak widać, możliwe jest usunięcie pary klucz / wartość z Hash @a, ponieważ dodaj nowe klucze, zmień wartości, eccetera. Ale nie możesz wskazać nowego obiektu, ponieważ jest to zmienna instancji tylko do odczytu.

Korsmakolnikov
źródło
13

Nie zawsze chcesz, aby zmienne instancji były w pełni dostępne spoza klasy. Istnieje wiele przypadków, w których zezwala się na dostęp do odczytu do zmiennej instancji, ale zapis do niej może nie być (np. Model, który pobiera dane ze źródła tylko do odczytu). Są przypadki, w których chcesz czegoś przeciwnego, ale nie mogę wymyślić żadnych, które nie są wymyślone z góry mojej głowy.

coreyward
źródło