Akcesoria i modyfikatory (znane również jako setery i gettery) są przydatne z trzech głównych powodów:
- Ograniczają dostęp do zmiennych.
- Na przykład można uzyskać dostęp do zmiennej, ale nie można jej modyfikować.
- Sprawdzają poprawność parametrów.
- Mogą powodować pewne działania niepożądane.
Uniwersytety, kursy online, samouczki, artykuły na blogach i przykłady kodu w Internecie kładą nacisk na znaczenie akcesoriów i modyfikatorów, które w dzisiejszych czasach są niemal „obowiązkowe” dla kodu. Można je więc znaleźć, nawet jeśli nie dostarczają żadnej dodatkowej wartości, jak na przykład poniższy kod.
public class Cat {
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
To powiedziawszy, bardzo często można znaleźć bardziej przydatne modyfikatory, te, które faktycznie sprawdzają parametry i rzucają wyjątek lub zwracają wartość logiczną, jeśli podano nieprawidłowe dane wejściowe, coś takiego:
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
Ale nawet wtedy prawie nigdy nie widzę, aby modyfikatory były wywoływane z konstruktora, więc najczęstszym przykładem prostej klasy, z którą mam do czynienia, jest:
public class Cat {
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Ale można by pomyśleć, że to drugie podejście jest znacznie bezpieczniejsze:
public class Cat {
private int age;
public Cat(int age) {
//Use the modifier instead of assigning the value directly.
setAge(age);
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Czy widzisz podobny wzorzec w swoim doświadczeniu, czy tylko ja mam pecha? A jeśli tak, to co według ciebie jest przyczyną? Czy jest oczywista wada używania modyfikatorów konstruktorów, czy też są one po prostu uważane za bezpieczniejsze? Czy to coś innego?
źródło
Odpowiedzi:
Bardzo ogólne rozumowanie filozoficzne
Zazwyczaj prosimy, aby konstruktor zapewnił (jako warunki dodatkowe) pewne gwarancje dotyczące stanu zbudowanego obiektu.
Zazwyczaj spodziewamy się również, że metody instancji mogą założyć (jako warunki wstępne), że te gwarancje już istnieją, gdy są wywoływane, i muszą tylko upewnić się, że ich nie złamią.
Wywołanie metody instancji z wnętrza konstruktora oznacza, że niektóre lub wszystkie z tych gwarancji mogły jeszcze nie zostać ustanowione, co utrudnia rozumowanie, czy warunki wstępne metody instancji są spełnione. Nawet jeśli dobrze to zrobisz, może być bardzo delikatny w obliczu np. zmiana kolejności wywołań instancji lub innych operacji.
Języki różnią się także sposobem, w jaki przetwarzają wywołania metod instancji, które są dziedziczone z klas podstawowych / zastępowane przez podklasy, podczas gdy konstruktor nadal działa. To dodaje kolejną warstwę złożoności.
Konkretne przykłady
Twój własny przykład tego, jak według ciebie powinno to wyglądać, jest błędny:
nie sprawdza to zwracanej wartości z
setAge
. Najwyraźniej wezwanie setera nie jest wcale gwarancją poprawności.Bardzo łatwe błędy, takie jak zależność od kolejności inicjalizacji, takie jak:
gdzie moje tymczasowe logowanie zepsuło wszystko. Ups!
Istnieją również języki takie jak C ++, w których wywoływanie settera z konstruktora oznacza zmarnowaną domyślną inicjalizację (której w przypadku niektórych zmiennych składowych warto unikać)
Prosta propozycja
Prawdą jest, że większość kodu nie jest napisana w ten sposób, ale jeśli chcesz zachować swój konstruktor w czystości i przewidywalności, i nadal używać logiki przed i po warunku, lepszym rozwiązaniem jest:
lub nawet lepiej, jeśli to możliwe: zakoduj ograniczenie w typie właściwości i poproś o sprawdzenie jego własnej wartości przy przypisaniu:
I wreszcie, dla pełnej kompletności, samo-sprawdzające się opakowanie w C ++. Zauważ, że chociaż nadal wykonuje żmudne sprawdzanie poprawności, ponieważ ta klasa nie robi nic więcej , jest stosunkowo łatwa do sprawdzenia
OK, to naprawdę nie jest kompletne, pominąłem różne konstruktory i zadania kopiowania i przenoszenia.
źródło
Kiedy obiekt jest konstruowany, z definicji nie jest w pełni uformowany. Zastanów się, w jaki sposób ustawiający podałeś:
Co zrobić, jeśli część walidacji
setAge()
obejmowała sprawdzenieage
nieruchomości, aby upewnić się, że obiekt może się tylko starzeć? Ten stan może wyglądać następująco:To wydaje się dość niewinną zmianą, ponieważ koty mogą przemieszczać się w czasie tylko w jednym kierunku. Jedynym problemem jest to, że nie można go użyć do ustawienia wartości początkowej,
this.age
ponieważ zakłada, żethis.age
ma już prawidłową wartość.Unikanie seterów w konstruktorach wyjaśnia, że jedyne, co robi konstruktor, to ustawianie zmiennej instancji i pomijanie innych zachowań, które mają miejsce w seterach.
źródło
Kilka dobrych odpowiedzi już tutaj, ale jak dotąd nikt nie zauważył, że pytasz tutaj o dwie rzeczy w jednym, co powoduje pewne zamieszanie. IMHO twój post najlepiej postrzegać jako dwa osobne pytania:
jeśli istnieje sprawdzanie poprawności ograniczenia w seterze, czy nie powinno ono również znajdować się w konstruktorze klasy, aby upewnić się, że nie można go ominąć?
dlaczego nie wywołać Settera bezpośrednio w tym celu z konstruktora?
Odpowiedź na pierwsze pytania jest jednoznaczna „tak”. Konstruktor, jeśli nie zgłosi wyjątku, powinien pozostawić obiekt w poprawnym stanie, a jeśli pewne wartości są zabronione dla niektórych atrybutów, nie ma absolutnie żadnego sensu pozwalać ctorowi na obejście tego.
Jednak odpowiedź na drugie pytanie brzmi zazwyczaj „nie”, pod warunkiem, że nie podejmie się środków, aby uniknąć zastąpienia setera w klasie pochodnej. Dlatego lepszą alternatywą jest zaimplementowanie sprawdzania poprawności ograniczeń w metodzie prywatnej, która może być ponownie użyta zarówno z narzędzia ustawiającego, jak i z konstruktora. Nie zamierzam tutaj powtarzać przykładu @Useless, który pokazuje dokładnie ten projekt.
źródło
Oto głupiutki kod Java, który pokazuje rodzaje problemów, na które można natknąć się przy użyciu niekończących metod w konstruktorze:
Kiedy to uruchomię, otrzymuję NPE w konstruktorze NextProblem. Jest to oczywiście trywialny przykład, ale sprawy mogą szybko się skomplikować, jeśli masz wiele poziomów dziedziczenia.
Myślę, że głównym powodem, dla którego nie stało się to powszechne, jest fakt, że kod jest trudniejszy do zrozumienia. Osobiście prawie nigdy nie mam metod ustawiających, a moje zmienne składowe są prawie niezmiennie (zamierzone) ostateczne. Z tego powodu wartości muszą być ustawione w konstruktorze. Jeśli używasz niezmiennych obiektów (i jest wiele dobrych powodów, aby to zrobić), pytanie jest dyskusyjne.
W każdym razie dobrym celem jest ponowne użycie sprawdzania poprawności lub innej logiki, można też zastosować metodę statyczną i wywołać ją zarówno z konstruktora, jak i ustawiacza.
źródło
name
pola).this
odniesienia do jakiejś innej metody, ponieważ nie można być pewnym, że jest ona w pełni zainicjowana.To zależy!
Jeśli przeprowadzasz prostą weryfikację lub wywołujesz nieprzyjemne skutki uboczne w seterach, które muszą być trafiane za każdym razem, gdy ustawiana jest właściwość: użyj setera. Alternatywą jest na ogół wyodrębnienie logiki settera z setera i wywołanie nowej metody, a następnie faktycznego zestawu zarówno z settera, jak i konstruktora - co w rzeczywistości jest takie samo, jak użycie settera! (Tyle że teraz trzymasz, co prawda mały, dwuwierszowy seter w dwóch miejscach).
Jednak , jeśli trzeba wykonywać skomplikowanych operacji w celu ustalenia przedmiotu przed konkretnym seter (lub dwa!) Mogłaby z powodzeniem wykonać, nie używać setter.
I w obu przypadkach mieć przypadki testowe . Bez względu na wybraną trasę, jeśli masz testy jednostkowe, będziesz mieć dużą szansę na ujawnienie pułapek związanych z którąkolwiek z tych opcji.
Dodam też, że jeśli moja pamięć z tak odległych czasów jest nawet bardzo dokładna, to właśnie takiego rozumowania nauczyłem się na studiach. I wcale nie jest to niezgodne z udanymi projektami, nad którymi miałem przyjemność pracować.
Gdybym musiał zgadywać, w pewnym momencie przeoczyłem notatkę „czystego kodu”, która zidentyfikowała niektóre typowe błędy, które mogą wynikać z używania setterów w konstruktorze. Ale ze swojej strony nie padłem ofiarą takich błędów ...
Twierdzę więc, że ta konkretna decyzja nie musi być przesadna i dogmatyczna.
źródło