Kiedy Gettery i Settery są uzasadnione

175

Getters i setery są często krytykowane jako nieodpowiednie OO. Z drugiej strony większość kodu OO, który widziałem, zawiera rozległe programy pobierające i ustawiające.

Kiedy pobierający i ustawiający są uzasadnieni? Czy starasz się ich nie używać? Czy są ogólnie nadużywane?

Jeśli twój ulubiony język ma właściwości (mój ma), wówczas takie rzeczy są również uważane za pobierające i ustawiające dla tego pytania. Są to samo z perspektywy metodologii OO. Po prostu mają ładniejszą składnię.

Źródła krytyki Gettera / Settera (niektóre wzięto z komentarzy, aby poprawić ich widoczność):

Aby wyrazić krytykę po prostu: Getters and Setters pozwalają manipulować stanem wewnętrznym obiektów spoza obiektu. To narusza enkapsulację. Tylko sam obiekt powinien dbać o swój stan wewnętrzny.

I przykładowa wersja proceduralna kodu.

struct Fridge
{
    int cheese;
}

void go_shopping(Fridge fridge)
{
     fridge.cheese += 5;
}

Wersja kodu mutatora:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

Programy pobierające i ustawiające znacznie skomplikowały kod bez zapewnienia odpowiedniego hermetyzacji. Ponieważ stan wewnętrzny jest dostępny dla innych obiektów, niewiele zyskujemy przez dodanie tych pobierających i ustawiających.

Pytanie zostało wcześniej omówione na temat przepełnienia stosu:

Winston Ewert
źródło
21
Getters and setters are often criticized as being not proper OO- Proszę o cytowanie.
Robert Harvey
45
@ Job, dlaczego, ponieważ ma kod? To dobre pytanie i smród świętej wojny, jeśli potraktowane poważnie.
Peter Turner
2
@Winston Ewert: „... pytanie brzmi, czy w ogóle powinieneś uzyskiwać dostęp do danych wewnątrz klasy.”: No cóż, nikt nie zmusza cię do implementacji funkcji pobierających i ustawiających dla wszystkich zmiennych składowych, implementujesz tylko te, których potrzebujesz. Dlatego korzystasz z metod pobierających i ustawiających zamiast publicznych zmiennych członków.
Giorgio,
2
@Winston Ewert: Nie rozumiem, jak brak pobierających / ustawiających rozwiązałby ten problem: musi istnieć sposób na dostęp do każdej części danych, w przeciwnym razie jest to bezużyteczne; dobrym zadaniem jest określenie, która część danych jest dostępna dla której części kodu. Jeśli ktoś jest złym projektantem, będzie taki z lub bez getterów i seterów. Tylko moje 2 centy.
Giorgio,

Odpowiedzi:

162

Posiadanie getterów i seterów samo w sobie nie przerywa enkapsulacji. To, co przerywa enkapsulację, to automatyczne dodawanie gettera i settera dla każdego członka danych (każdego pola , w języku Java), bez zastanowienia. Jest to lepsze niż upublicznienie wszystkich członków danych, ale to tylko niewielki krok.

Istotą enkapsulacji nie jest to, że nie powinieneś być w stanie poznać ani zmienić stanu obiektu spoza obiektu, ale że powinieneś mieć rozsądne zasady, aby to zrobić.

  • Niektóre elementy danych mogą być całkowicie wewnętrzne w stosunku do obiektu i nie powinny mieć modułów pobierających ani ustawiających.

  • Niektóre elementy danych powinny być tylko do odczytu, więc mogą potrzebować modułów pobierających, ale nie ustawiających.

  • Niektóre elementy danych mogą wymagać zachowania spójności. W takim przypadku nie zapewniłbyś dla każdego ustawienia, ale pojedynczą metodę ustawiania ich w tym samym czasie, abyś mógł sprawdzić wartości pod kątem spójności.

  • Niektóre elementy danych mogą wymagać zmiany tylko w określony sposób, na przykład zwiększane lub zmniejszane o określoną kwotę. W takim przypadku podasz metodę increment()i / lub decrement()metodę, a nie ustawiającą.

  • Jednak inni mogą potrzebować odczytu i zapisu i mieliby zarówno gettera, jak i setera.

Rozważ przykład class Person. Powiedzmy, że dana osoba ma nazwisko, numer ubezpieczenia społecznego i wiek. Powiedzmy, że nie zezwalamy ludziom na zmianę nazwiska lub numeru ubezpieczenia społecznego. Jednak wiek osoby powinien być zwiększany o 1 każdego roku. W takim przypadku podasz konstruktor, który zainicjuje nazwę i SSN zgodnie z podanymi wartościami, i który zainicjuje wiek do 0. Podałbyś również metodę incrementAge(), która zwiększyłaby wiek o 1. Podałbyś także getters dla wszystkich trzech. W tym przypadku nie są wymagane ustawienia.

W tym projekcie zezwalasz na sprawdzanie stanu obiektu spoza klasy i pozwalasz na jego zmianę spoza klasy. Nie zezwalasz jednak na arbitralną zmianę stanu. Istnieje polityka, która skutecznie stwierdza, że ​​nazwy i numeru SSN nie można w ogóle zmienić, a wiek można zwiększyć o 1 rok na raz.

Powiedzmy teraz, że dana osoba ma również pensję. Ludzie mogą zmieniać pracę do woli, co oznacza, że ​​ich wynagrodzenie również ulegnie zmianie. Aby wymodelować tę sytuację, nie mamy innego wyjścia, jak zapewnić setSalary()metodę! Zezwolenie na dowolną zmianę wynagrodzenia jest w tym przypadku całkowicie rozsądną polityką.

Nawiasem mówiąc, w swoim przykładzie, dałbym klasę i metod, zamiast i . Wtedy nadal miałbyś enkapsulację.FridgeputCheese()takeCheese()get_cheese()set_cheese()


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}
Dave Jarvis
źródło
4
Proszę, nie traktuj mojego przykładu z lodówki zbyt poważnie. :) Prawdziwa lodówka powinna być pojemnikiem, spodziewałbym się, że będzie wiedział, jak trzymać przedmioty, nie martwiąc się o to, czym dokładnie są. IE byłoby jak ArrayList. Jeśli chodzi o to, co sprawia, że ​​jest lodówką, powiedzmy, że serializuje obiekty na dysk podczas przechowywania, aby przetrwały awarię systemu. Dobrze. Wystarczy potraktować poważnie mój przykład lodówki.
Winston Ewert
6
Ale czy lodówka nie powinna znać terminów przydatności, aby mogła powiedzieć, że ser się zepsuł i że należy go wyrzucić? :) Ok, musimy to zatrzymać! :)
Dima
10
Całkiem interesująca dyskusja na temat logiki lodówki , o której tu
mowa
35
Haha, chciałbym zobaczyć taką reklamę: „To zupełnie nowy rodzaj lodówki: rzuca się w ciebie, gdy próbujesz złapać coś, czego nie ma! W ten sposób spróbujesz tylko raz! Martwisz się napychając lodówkę i pozwól lodówce martwić się nielegalnym zachowaniem! ”
gablin
42
W przykładzie wszystko jest źle. Nie powinno być pola Age ani metody setAge (). Wiek jest funkcją daty urodzenia osoby w porównaniu do pewnego momentu w czasie. Choć pozornie trywialne, jest to dokładnie to, co jest złe we współczesnej praktyce pełnej zmienności obiektów i innych złych projektów, co widać za pomocą metod get / set dla prywatnych pól w porównaniu do uważnego rozważenia, co jest naprawdę zmienne, co jest polem, co jest przedmiotem powinien wiedzieć o swoich zachowaniach i jakie zmiany stanu są faktycznie poprawne (które metody setX całkowicie niszczą).
Darrell Teague,
41

Podstawowy powód pobierania i ustawiania w Javie jest bardzo prosty:

  • W interfejsie można określić tylko metody , a nie pola.

Dlatego jeśli chcesz pozwolić, aby pole przechodziło przez interfejs, będziesz potrzebował metody czytnika i zapisu. Są one tradycyjnie nazywane getX i setX dla pola x.

użytkownik1249
źródło
44
To ograniczenie językowe. Prawdziwe pytanie brzmi, czy powinniśmy pozwolić innym obiektom na manipulowanie tym stanem.
Winston Ewert
3
@Peter Turner, oczywiście nic nie stoi na przeszkodzie, aby język posiadał interfejsy zawierające właściwości. Pod osłonami, które mogą być równie dobrze zaimplementowane jako getter / setter, ale łatwo byłoby dodać obsługę definicji interfejsów dla właściwości.
Winston Ewert
2
@Winston, w końcu będziesz musiał pozwolić klasom na przesyłanie informacji tam iz powrotem, aby faktycznie wykonać pracę. Co zasugerowałbyś zamiast tego?
6
Obiekt powinien zapewniać mu interfejs wyższego poziomu. Gettery i Settery są zazwyczaj interfejsem niskiego poziomu. Załóżmy na przykład, że masz klasę implementującą drzewo binarne. Możesz mieć funkcje takie jak GetLeft (), GetRight (), GetValue () i GetKey (), aby poruszać się po drzewie. Ale to jest całkowicie niewłaściwe podejście. Twoja klasa drzewa binarnego powinna zapewniać operacje takie jak Znajdź, Wstaw, Usuń.
Winston Ewert
6
Aby wziąć inny przykład, rozważ kawałek tetris. Kawałek tetris ma pewien stan wewnętrzny, taki jak typ_bloku, obrót, położenie. Możesz mieć gettery takie jak GetBlockType (), GetRotation (), GetPosition (). Ale tak naprawdę nie powinieneś. Powinieneś mieć metodę GetSquares (), która zwraca listę wszystkich kwadratów zajmowanych przez ten element. Nie powinieneś także mieć rzeczy takich jak SetPosition () lub SetRotation (). Zamiast tego powinieneś mieć operacje takie jak MoveLeft (), MoveRight (), Rotate (), Fall ().
Winston Ewert
20

Od http://www.adam-bien.com/roller/abien/entry/encapsulation_violation_with_getters_and

Styl JavaBean:

connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();

Styl OO:

connection.connect("dukie","duke");

Cóż, wyraźnie wolę to drugie podejście; nie upuszcza szczegółów implementacji, jest prostszy i bardziej zwięzły, a wszystkie potrzebne informacje są zawarte w wywołaniu metody, więc łatwiej jest to zrobić poprawnie. W miarę możliwości wolę także ustawiać członków prywatnych przy użyciu parametrów w konstruktorze.

Twoje pytanie brzmi, kiedy uzasadnienie gettera / settera jest uzasadnione? Być może, gdy potrzebna jest zmiana trybu lub konieczne jest przesłuchanie obiektu w celu uzyskania informacji.

myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;

Myśląc o tym, kiedy po raz pierwszy zacząłem kodować w C #, napisałem dużo kodu w stylu Javabeans zilustrowanym powyżej. Ale kiedy zdobywałem doświadczenie w języku, zacząłem robić więcej ustawień członków w konstruktorze i używać metod, które wyglądały bardziej jak powyższy styl OO.

Robert Harvey
źródło
6
Dlaczego styl OO „podaj wszystkie argumenty jako parametry”?
5
Podczas gdy twój „styl OO” jest odpowiedni dla 2 lub 3 opcji, wolałbym styl JavaBean, gdy przekroczysz ten poziom. Posiadanie 20 przeciążonych metod dla każdej możliwej kombinacji parametrów po prostu wygląda okropnie
TheLQ
1
@TheLQ: C # 4.0 ułatwia to, umożliwiając opcjonalne i nazwane parametry w wywołaniach konstruktora i metod.
Robert Harvey
7
@TheLQ Do tego właśnie służy Konstruktor z płynną składnią, patrz np . MapMaker firmy Guava .
maaartinus,
5
@ Thorbjørn Ravn Andersen, powiedziałbym, że „podanie wszystkich argumentów jako parametrów” jest lepsze w stylu OO niż „wywołanie seterów JavaBean”, ponieważ setter sugeruje, że jest ustawiony atrybut bazowy, który z definicji ujawnia szczegóły wewnętrzne. Zastosowanie metody connect (..) w tym przykładzie nie zakłada takiego założenia; może ustawiasz atrybuty użytkownika i hasła, a może nie. Ważne jest, aby skupić się na właściwej koncepcji OO: komunikat „połącz”.
Andres F.,
13

Kiedy pobierający i ustawiający są uzasadnieni?

Kiedy zachowanie „get” i „set” faktycznie pasuje do zachowania w twoim modelu, co praktycznie nigdy nie jest możliwe .

Każde inne użycie jest oszustwem, ponieważ zachowanie domeny biznesowej nie jest jasne.

Edytować

Ta odpowiedź mogła okazać się nieporadna, więc pozwól mi rozwinąć. Powyższe odpowiedzi są w większości poprawne, ale koncentrują się na paradygmatach programistycznych projektowania OO i niektórych, które pomijają duży obraz. Z mojego doświadczenia wynika, że ​​ludzie sądzą, że unikanie spotkań i seterów jest pewną akademicką zasadą dla języków programowania OO (np. Ludzie myślą, że wymiana getterów na właściwości jest wielka)

W rzeczywistości jest to konsekwencja procesu projektowania. Nie musisz wchodzić w interfejsy, enkapsulację i wzorce, kłócić się o to, czy to łamie lub nie łamie tych paradygmatów, a co jest dobrym programowaniem OO. Jedynym punktem, który ostatecznie ma znaczenie, jest to, że jeśli w Twojej domenie nie ma nic, co działałoby w ten sposób, umieszczając je w domenie, nie modelujesz domeny.

Rzeczywistość jest taka, że ​​jest mało prawdopodobne, aby jakikolwiek system w Twojej domenie miał funkcje pobierające i ustawiające. Nie możesz podejść do mężczyzny lub kobiety odpowiedzialnej za płacę i po prostu powiedzieć „Ustaw tę pensję na X” lub „Zdobądź tę pensję” . Takie zachowanie po prostu nie istnieje

Jeśli wstawiasz to do swojego kodu, nie projektujesz systemu tak, aby pasował do modelu Twojej domeny. Tak, to zrywa interfejsy i enkapsulację, ale nie o to chodzi. Chodzi o to, że modelujesz coś, co nie istnieje.

Co więcej, prawdopodobnie brakuje ci ważnego kroku lub procesu, ponieważ prawdopodobnie istnieje powód, dla którego nie mogę po prostu podejść do listy wypłat i powiedzieć, że ustaw tę pensję na X.

Kiedy ludzie używają getterów i seterów, mają tendencję do wypychania reguł tego procesu w niewłaściwe miejsce. To jeszcze bardziej odchodzi od Twojej domeny. Na przykładzie z prawdziwego świata przypomina to pensję, zakładając, że przypadkowa osoba, która weszła, ma pozwolenie na uzyskanie tych wartości, inaczej nie poprosiłby o nie. Domena jest nie tylko taka, jak domena, ale w rzeczywistości kłamie na temat jej właściwości.

Cormac Mulhall
źródło
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami poczynionymi i wyjaśnionymi w poprzednich 17 odpowiedziach
gnat
1
Pozostałe odpowiedzi mówią o obiektach, interfejsach i paradygmatach innych języków. Co jest w porządku, ale nie jest to główny problem. Gettery i setery są nie tylko złe, ponieważ łamią niektóre konwencje kodowania, ale przede wszystkim dlatego, że rzadko spotykają się z tym zachowaniem w reprezentowanym modelu. Ten punkt gubi się w dyskusji na temat najlepszych praktyk kodowania.
Cormac Mulhall
Jak myślisz, czym jest interfejs płac, jeśli nie jest to set/getwynagrodzenie?
Winston Ewert
1
Jeśli są one owinięte wokół warstw autoryzacji i ograniczone do niektórych podmiotów zewnętrznych, nie są obiektami pobierającymi i ustawiającymi. Nie twierdzę, że nie ma żadnej metody w dziale płac, która zmienia wynagrodzenie, ale ta metoda powinna być zgodna z wewnętrznymi procesami stosowanymi w samej firmie. Na przykład większość firm ustala wynagrodzenie na podstawie umowy o pracę, która jest autoryzowana przez kierownika. Jeśli wynagrodzenie pracownika ulegnie zmianie, sporządzana jest nowa umowa, którą musi podpisać kierownik. Dlatego lista płac powinna oczekiwać, że obiekt kontraktu i jakiś obiekt autoryzacji zmienią listę wypłat
Cormac Mulhall
3
Innymi słowy, istnieje znaczna różnica między set_salary (new_salary, pracownik_id) i autoryzowaną_salary_update (umowa o pracowniku, manager_autoryzujący). Proces powinien modelować wewnętrzny proces biznesowy
Cormac Mulhall
11

Zasadniczo gettery i setery są złym pomysłem. Jeśli pole nie jest logicznie częścią interfejsu, a Ty ustawisz go jako prywatny, nie ma sprawy. Jeśli logicznie jest to część interfejsu, a Ty upubliczniasz to nie ma sprawy. Ale jeśli ustawisz go jako prywatny, a następnie odwrócisz się i ponownie uczynisz go efektywnym publicznym, udostępniając narzędzie pobierające i ustawiające, wrócisz do miejsca, w którym zacząłeś, z wyjątkiem tego, że kod jest teraz bardziej szczegółowy i zaciemniony.

Oczywiście są wyjątki. W Javie może być konieczne użycie interfejsów. Standardowa biblioteka Java ma wymagania dotyczące kompatybilności wstecznej, tak wysokie, że przewyższają normalne miary jakości kodu. Możliwe jest nawet, że rzeczywiście masz do czynienia z legendarnym, ale rzadkim przypadkiem, w którym istnieje duża szansa, że ​​możesz później zastąpić zapisane pole obliczeniem w locie, nie naruszając w inny sposób interfejsu. Ale to są wyjątki. Gettery i setery to anty-wzór, który wymaga specjalnego uzasadnienia.

rwallace
źródło
6
Funkcja pobierająca i ustawiająca oznacza, że ​​atrybut, a nie pole, jest częścią interfejsu. Załóżmy, że getBirthDate () i SetBirthDate () zajmują „rrrr / mm / dd”, ale przechowywane pole to liczba dni od 01.01.1000. Możesz zmienić to na 01/01/0001, a getter i setter utrzymają ten sam interfejs. Zwłaszcza, że ​​„public” oznacza publiczne śmieci, zmienne nigdy nie powinny być publiczne; zawsze używaj narzędzia ustawiającego do sprawdzania wartości wejściowej, aktualizuj wszystkie powiązane pola, konwertuj w razie potrzeby itp. Spróbuj użyć narzędzia pobierającego na wypadek zmiany pamięci wewnętrznej.
Andy Canfield
@AndyCanfield, co jest nieco mocne, by powiedzieć, że publiczne oznacza, że ​​można je publicznie wyrzucić do śmieci. Tylko jeśli źle go zaprogramujesz. Jeśli masz seter i do niego wysyłana jest niewłaściwa wartość, możesz zgłosić wyjątek, który możesz wywołać, niszcząc program, ale kończy się z błędem Również jeśli użytkownik obiektu może nadać zmiennej wartość i cud dać jej niewłaściwy wartość, możesz wziąć to pod uwagę i przetestować. u może powiedzieć „ah, ale muszę wykonać tylko 1 test, jeśli używam setera”, ale być może twój własny kod nadaje zmiennej złą wartość. więc seter może nie zredukuj swoje testy.n dzięki testom, twój prog nie jest „
niszczony
3

to, czy pole jest dostępne bezpośrednio czy za pomocą metody, nie jest tak naprawdę ważne.

Niezmienniki klasowe (przydatne) są ważne. Aby je zachować, czasami nie musimy być w stanie zmienić czegoś z zewnątrz. Na przykład. jeśli mamy klasę Kwadrat z separete szerokością i wysokością, zmiana jednego z nich powoduje, że staje się czymś innym niż kwadrat. Potrzebujemy więc metody changeSide Gdyby to był Rectangle, moglibyśmy mieć setters / public field. Ale seter sprawdziłby, czy jego wartość większa niż zero byłaby lepsza.

A w konkretnych językach (np. Java) są powody, dla których potrzebujemy tych metod (interfejsów). Innym powodem tej metody jest zgodność (źródłowa i binarna). Łatwiej je więc dodać, niż pomyśleć, czy wystarczy pole publiczne.

btw. Lubię używać prostych klas przechowywania niezmiennej wartości z publicznymi polami końcowymi.

użytkownik470365
źródło
To właściwie jedyna rzecz, której należy unikać. Przejście z pola do właściwości / gettera / settera jest przełomową zmianą w większości języków.
MrDosu
2

Możesz zmienić ustawienia wewnętrzne na cokolwiek, zachowując interfejsy bez zmian. Jeśli twoje interfejsy się nie różnią, kod, który kodujesz, nie ulegnie uszkodzeniu. Nadal możesz zmieniać swoje elementy wewnętrzne, jak chcesz.

George Silva
źródło
1
Dziwię się, że ta odpowiedź jest tak daleko. Gettery i setters pozwalają ci robić więcej w przyszłości (np. Odpalić wydarzenie, zrobić więcej weryfikacji danych wejściowych, zrobić wewnętrzną księgowość) bez zerwania twojego istniejącego API. Nawet przy niepublicznym kodzie, im mniej interfejsów API musisz zmienić, tym mniej zależności do zaktualizowania i tym mniej błędów wprowadzonych podczas tych aktualizacji.
AmadeusDrZaius
0

Moje podejście jest takie -

Kiedy spodziewam się, że z czasem skończę z danymi, getter / setter jest uzasadniony. Ponadto, jeśli zachodzi zmiana, często wpycham dane do gettera / settera.

Jeśli jest to struktura POD, pozostawiam wolne miejsca.

Na bardziej abstrakcyjnym poziomie pytanie brzmi: „kto zarządza danymi”, a to zależy od projektu.

Paul Nathan
źródło
0

Jeśli używanie programów pobierających i ustawiających wydaje się skomplikowane, problemem może być język, a nie sama koncepcja.

Oto kod z drugiego przykładu napisanego w Ruby:

class Fridge
  attr_accessor :cheese
end

def go_shopping fridge
  fridge.cheese += 5
end

Zauważ, że wygląda to bardzo podobnie do pierwszego przykładu w Javie? Gdy osoby pobierające i ustawiające są traktowane jak obywatele pierwszej klasy, nie są obowiązkiem w użyciu, a dodatkowa elastyczność może czasem być prawdziwym dobrodziejstwem - na przykład możemy zdecydować o zwróceniu domyślnej wartości dla sera na nowej lodówce:

class Fridge
  attr_accessor :cheese

  def cheese
    @cheese || 0
  end
end

Oczywiście będzie wiele zmiennych, których w ogóle nie należy ujawniać publicznie. Niepotrzebne ujawnianie zmiennych wewnętrznych pogorszy kod, ale nie można winić tego za pobierające i ustawiające.

Tobias Cohen
źródło
To jest pięknie powiedziane. Dodano funkcje konstruktora, ponieważ chcemy tworzyć niezmienne obiekty, stopniowo budując przepis: może jeśli dodasz sztuczny słodzik, nie musisz dodawać cukru. Używając nazwanych parametrów, a nawet przy przeciążeniu, bardzo trudno jest zbudować jedną metodę wykonującą wszystko. I oczywiście java nie ma nazwanych parametrów, stąd jeszcze jeden powód, dla którego ludzie używają wzorca Builder.
YoYo
0

Rozważ Sizeklasę, która ujmuje szerokość i wysokość. Mogę wyeliminować setery za pomocą konstruktora, ale w jaki sposób pomaga mi to narysować prostokąt Size? Szerokość i wysokość nie są danymi wewnętrznymi dla klasy; są to udostępniane dane, które muszą być dostępne dla konsumentów Size.

Obiekty składają się z zachowań i stanu - lub atrybutów. Jeśli nie ma stanu narażonego, tylko zachowania są publiczne.

Bez stanu, w jaki sposób posortowałbyś kolekcję obiektów? Jak szukałbyś określonego wystąpienia obiektu? Jeśli używasz tylko konstruktorów, co się stanie, gdy twój obiekt ma długą listę atrybutów?

Żaden parametr metody nie powinien być nigdy używany bez sprawdzania poprawności. Dlatego lenistwo pisze:

setMyField(int myField){
    this.myField = myField;
}

Jeśli napiszesz to w ten sposób, przynajmniej przygotowałeś się do użycia settera do sprawdzania poprawności; to lepsze niż pole publiczne - ale ledwo. Ale przynajmniej masz spójny interfejs publiczny, do którego możesz wrócić i wprowadzić reguły sprawdzania poprawności bez łamania kodu klientów.

Gettery, setery, właściwości, mutatory, nazywaj ich jak chcesz, ale są konieczne.

Dale
źródło
Ale przynajmniej masz spójny interfejs publiczny, do którego możesz wrócić i wprowadzić reguły sprawdzania poprawności bez łamania kodu klientów. To nieprawda. Dodanie reguł sprawdzania poprawności w dalszej części drogi może bardzo dobrze „złamać kod klientów”. Weźmy na intprzykład zmienną instancji i wyobraź sobie, że zdecydujesz się dodać regułę sprawdzania poprawności, aby akceptować tylko wartości nieujemne. Problem polega na tym, że kod klienta może już polegać na możliwości ustawienia go na wartość ujemną ...
jub0bs
0

Jeśli gettery i settery naruszają enkapsulację i prawdziwe OO, to mam poważne kłopoty.

Zawsze czułem, że przedmiot reprezentuje wszystko, co przychodzi ci na myśl, czego potrzebujesz.

Właśnie skończyłem pisać program, który generuje Mazes w Javie, i mam klasę reprezentującą „Maze Squares”. Mam dane w tej klasie, które reprezentują współrzędne, ściany i booleany itp.

Muszę mieć sposób na zmianę / manipulację / dostęp do tych danych! Co mam zrobić bez getterów i seterów? Java nie korzysta z właściwości, a ustawienie wszystkich moich danych lokalnych dla tej klasy na publiczne jest ZDECYDOWANIE naruszeniem enkapsulacji i OO.

Bryan Harrington
źródło
6
Oto pytanie: Jeśli upublicznisz wszystkie te pola i przestaniesz używać programów pobierających i ustawiających, jak różni się Twój kod?
Winston Ewert
Teoretycznie nie. Po co w ogóle tworzyć klasę? Ten sam argument można wysunąć na cokolwiek. Ale post pod moim wydaje się, że powiedział to bardziej elegancko. (Gettery i setery są sposobem na bezpieczne przekazanie wartości do obiektu lub odzyskanie wartości z tego obiektu.) Post jest kontynuowany ..
Bryan Harrington
3
Zwracam uwagę: ten plakat ostatecznie się ze mną zgodził i zamieścił inną odpowiedź na ten temat. Zasadniczo używanie programów pobierających i ustawiających jest równoznaczne z bezpośrednim manipulowaniem danymi. Robiąc to, tak naprawdę nie zyskujesz OOness. Aby być OO, należy zapewnić interfejs danych wyższego poziomu.
Winston Ewert
4
Myślę, że najlepszym sposobem na opisanie tego byłoby powiedzenie, że powinieneś projektować swoje interfejsy od zewnątrz, a nie od wewnątrz. Interfejs zapewniany przez obiekt powinien być podyktowany sposobem użycia obiektu, a nie sposobem jego implementacji. Więc nie pisz getterów i pozwól obiektowi zewnętrznemu interpretować dane. Dowiedz się, na jakie pytanie należy odpowiedzieć, i napisz metodę, która odpowie na to pytanie. Nie zezwalaj obiektom zewnętrznym na modyfikowanie stanu przez ustawiającego, dowiedz się, jakiego rodzaju manipulacji potrzebują, i pisz metody, które to robią.
Winston Ewert
2
W niektórych przypadkach Getters i Setters są najlepszą metodą. Czasami są najlepszym interfejsem, jaki możesz zapewnić. Jednak w wielu przypadkach tak nie jest. W wielu przypadkach przeciekasz szczegóły implementacji do innych klas.
Winston Ewert
-1

Po pierwsze, są zasadniczo dwa typy obiektów, które będę komentować, wartości i typy usług.

Typy usług nigdy nie powinny mieć ustawień, bez względu na wymagane zależności nie można ich uzyskać. Najlepszym sposobem na przekazanie zależności jest użycie konstruktora lub fabryki, dzięki czemu wszystkie instancje są w pełni uformowane od początku, jasne i proste.

Typy wartości również powinny być niezmienne, z drugiej strony zdarzają się sytuacje, w których jest to niepraktyczne, takie jak ORM lub inne mapowanie, np. Z widżetu na obiekt. Wszystkie inne typy wartości, które są przekazywane dookoła systemu z jednej warstwy lub części na drugą, powinny być niezmienne i nie powinny mieć ustawiających.

MP01
źródło
Sugerowałbym trzeci typ: zmienny pojemnik, którego celem jest przechowywanie jednej lub więcej wartości; w wielu przypadkach nie należy oczekiwać, że kontener będzie miał wiele przeszkód w logice lub sprawdzaniu poprawności. Kod, który potrzebuje metody obliczenia sześciu dodatnich liczb całkowitych, może przekazać kontener na sześć liczb całkowitych do metody, która będzie przechowywać w nim wyniki. Odpowiedzialność za zapewnienie dodatnich liczb powinna zasadniczo spoczywać na dzwoniącym lub na wywoływanej metodzie, a nie na kontenerze.
supercat
-1

Getter / Setter jest uzasadniony, tak jak każda inna metoda publiczna jest uzasadniona. Uzasadnieniem jest głównie dlatego, że chcemy zapewnić interfejs do świata zewnętrznego, który określa sposób, w jaki nasza klasa współdziała z innymi klasami. Robimy to głównie dlatego, że chcemy zmniejszyć sprzężenie między osobnymi podmiotami. Zmniejszenie sprzężenia jest dobre z wielu powodów:

  • Mogę używać z mojego obecnego kodu tego samego interfejsu klasy, chociaż tymczasem klasa dodała nowe metody (ale zachowała stary interfejs)
  • Może zmienić wewnętrzną reprezentację klasy bez wpływu na jej konsumentów
  • Zmniejsz ryzyko błędów: jeśli uczynisz swoje dane wewnętrzne prywatnymi, możesz mieć pewność, że kod zewnętrzny nie zadziała z Twoimi danymi wewnętrznymi
Ghita
źródło
-1

Tak, metody pobierające i ustawiające to anty-wzorzec w programowaniu obiektowym i nigdy nie powinny być używane: http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html . Krótko mówiąc, nie pasują do paradygmatu obiektowego, ponieważ zachęcają do traktowania obiektu jak struktury danych. Co jest głównym nieporozumieniem.

yegor256
źródło
2
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami przedstawionymi i wyjaśnionymi w poprzednich 18 odpowiedziach
gnat
4
Jest również trochę mocne, aby powiedzieć nigdy .
Winston Ewert
-4

Mam nadzieję, że wszystko, co IDE opracowane przez firmę, która rozwija język programowania automatycznie generuje, nie narusza „Zasad orientacji obiektowej”.

To powiedziawszy, za każdym razem, gdy masz zmienną o zasięgu publicznym, użyj gettera i setera i jesteś złoty. Wydaje mi się, że jest niezbędnym elementem niezwykle zorientowanej obiektowo zasady enkapsulacji - nawet jeśli wygląda na dużo niepotrzebnego kodu.

Powodem ich użycia jest to, że może jeszcze nastąpić właściwe hermetyzowanie i tak długo, jak długo wyodrębnisz warstwę, która pozwala osobie małpującej z twoim przedmiotem poczuć twój obiekt, możesz zabrać ten obiekt bez jego wiedzy, prawda?

Wystarczy powiedzieć, że podzielony dom nie może znieść, jeden z lokatorów OO nie chce się włączyć i nie można służyć zarówno enkapsulacji, jak i przedwczesnej optymalizacji.

Peter Turner
źródło
4
i co dokładnie zyskałem, dodając dużo kodu do wywołania setFoo () i getFoo () za każdym razem, gdy mam do czynienia ze zmienną?
Winston Ewert
7
Chodzi o to, że nie sądzę, że dostajesz kapsułkowanie. Nadal manipulujesz stanem obiektu z innych miejsc. Po prostu robisz to bardziej gadatliwie. Aby uzyskać enkapsulację, musisz scentralizować manipulowanie wartościami w klasie, która jest właścicielem tej wartości. Wszystko inne to tylko kod proceduralny w odzieży OO. (Nie ma w tym nic złego, ale tak właśnie jest).
Winston Ewert
2
Zaletą enkapsulacji jest to, że cała logika związana z konkretnymi wartościami znajduje się w jednym miejscu (mniej więcej). Nie rozumiem tego z getterami i seterami. Dlatego nie sądzę, żebym czerpał z nich wiele korzyści.
Winston Ewert
1
Zgadzam się jednak, że wolę mieć kod z modułami pobierającymi i ustawiającymi, a nie taki, który swobodnie manipuluje danymi za pośrednictwem publicznego dostępu. W ten sposób mogę przynajmniej załączyć weryfikację przychodzących danych.
Winston Ewert
3
Z pewnością nie jestem fanem gadatliwości. Ale moim sprzeciwem są tak naprawdę ludzie, którzy ustawiają swoje zmienne jako prywatne, a następnie wywołują dla nich funkcje pobierające i ustawiające dowolnie, co kończy się praktycznie bez różnicy. Jeśli chcesz używać pobierających / ustawiających w swojej klasie, nie ma sprawy. Pytanie brzmi dlaczego? Główną korzyścią, o której wiem, jest to, że możesz dokonać pewnej weryfikacji danych, aby wcześniej wykryć błędy. Jest to mniej pomocne, gdy cały kod manipulujący danymi znajduje się w tym samym miejscu. Ale nadal może być pomocny. Najlepiej, jeśli masz język, który obsługuje właściwości i pozwala uniknąć gadatliwości.
Winston Ewert
-4

Myślałem, że będziemy tu mieli więcej zwykłych odpowiedzi?

Gettery i setery są na ogół bardzo OOP (każdy, kto mówi cokolwiek innego, jest źle, tak po prostu).

Chociaż musisz pamiętać, aby nie przesadzać z inżynierem, nigdy nie twórz gettera ani setera, który nie jest potrzebny. Wszelkie informacje, których nie będziesz używać przez inną klasę, dla których nie powinieneś mieć gettera ani setera.

Chociaż powodem, dla którego nie upubliczniasz pól, a zamiast tego masz metody pobierające i ustawiające, są w większości:

  • Nie można wykonać odpowiednich testów przy użyciu pól. Getters / setery są pod tym względem znacznie lepsze.

  • Niemożliwe jest prawidłowe użycie pól w ustawieniach programowania w czasie rzeczywistym. Zsynchronizowane urządzenia pobierające / ustawiające to właściwy sposób.

  • Temat interfejsu (już wyjaśniony, więc nie powiem).

  • Łatwiej jest go używać, gdy ponownie używasz systemu.

  • Znacznie trudniej jest wiedzieć, które klasy używają twojej klasy i co się zepsuje, jeśli zmienisz pole niż getter / setter.

Dlatego tylko raz korzystasz z pola publicznego zamiast gettera / setera, kiedy nie tworzysz oficjalnego projektu, ale robisz to dla zabawy i nie możesz zawracać sobie głowy geterem / seterami.

Skarion
źródło
2
+1 za testowalność. Zszokowany, że nikt wcześniej o tym nie wspominał. Kolejną przydatną funkcją właściwości jest ich debugowanie. (znacznie łatwiej wstawić punkt przerwania w kodzie niż wymagać punktów przerwania sprzętowego / pamięci dla pól).
Mark H
9
Zarzut przeciwko getterom i seterom polega na tym, że często są oni przyzwyczajeni do przestrzegania litery prawa OOP, ignorując ducha i intencje. OOP mówi, że nie powinieneś pozwalać innym przedmiotom na zmoczenie się z twoim wnętrzem. Jeśli zdefiniujesz metody pobierające i ustawiające, które i tak to robią, ostatecznie naruszysz OOP.
Winston Ewert
6
Zgadzam się, że istnieje wiele korzyści dla osób pobierających i ustawiających w porównaniu z prostymi polami publicznymi. Nie jestem jednak pewien, w jaki sposób testy są z nimi lepsze. Czy możesz wytłumaczyć?
Winston Ewert
4
@Skarion: Nie zgadzam się z twoją odpowiedzią. Wygląda na to, że prezentujesz dwie alternatywy: albo użycie getterów / setterów, albo upublicznienie pól. Ale jest trzecia opcja: nie używać getterów / setterów ani ujawniać pól! Dlaczego trzeba sprawdzać stan pola wewnętrznego? Czy nie powinieneś testować publicznego interfejsu API swojego obiektu?
Andres F.,