Czy metody klasy powinny wywoływać własne metody pobierające i ustawiające?

53

W miejscu pracy widzę wiele klas, które wykonują takie rzeczy:

public class ClassThatCallsItsOwnGettersAndSetters {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        setField("value");
        //do stuff
        String localField = getField();
        //do stuff with "localField"
    }
}

Gdybym napisał to od zera, methodWithLogic()zamiast tego napisałbym tak:

public class ClassThatUsesItsOwnFields {

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }

    public void methodWithLogic() {
        field = "value";
        //do stuff            
        //do stuff with "field"
    }
}

Wydaje mi się, że kiedy klasa wywołuje własne metody pobierające i ustawiające, utrudnia to odczytanie kodu. Dla mnie prawie implikuje to, że w tym wywołaniu metody dzieje się złożona logika, nawet w naszym przypadku prawie nigdy tak nie jest. Kiedy debuguję jakiś nieznany kod, kto może powiedzieć, że błąd nie jest efektem ubocznym tej metody? Innymi słowy, sprawia, że ​​biorę wiele dodatkowych podróży w podróż po zrozumieniu kodu.

Czy są zalety pierwszej metody? Czy pierwsza metoda jest rzeczywiście lepsza?

Daniel Kaplan
źródło
Kiedy debuguję jakiś nieznany kod, kto może powiedzieć, że błąd nie jest efektem ubocznym tej metody? Twoje testy jednostkowe. :)
Joshua Taylor
1
Użycie getters / setters w twoim wewnętrznym kodzie pozwala ci stworzyć punkt przerwania w twoim kodzie, jeśli przechodzisz przez niego z debuggerem. Pozwala także upewnić się, że jeśli coś pójdzie nie tak z ustawieniem / uzyskaniem wartości, wiesz, że dzieje się tak z powodu tej metody, a nie z powodu nieprawidłowego dostępu. Ogólnie zapewnia więcej kodu zgodności.
callyalater 13.04.16

Odpowiedzi:

46

Nie powiem, co jest lepsze lub gorsze, ponieważ częściowo zależy to od twojej sytuacji (moim zdaniem). Ale weź pod uwagę, że twoje programy pobierające i ustawiające mogą później zmienić implementację, a ominięcie ich pominie tę logikę.

Na przykład, co się stanie, jeśli dodasz „brudną” flagę do niektórych pól setera? Dzwoniąc do seterów w kodzie wewnętrznym, ustawisz brudną flagę bez zmiany innego kodu. W wielu sytuacjach byłoby to dobre.

Matt S.
źródło
Ale co z inicjowaniem danych w wewnętrznej bazie danych i nie chcesz, aby były one interpretowane jako brudne. Jeśli dzwoniłeś setField()od początku, właśnie wprowadziłeś błąd.
Daniel Kaplan
6
Właśnie dlatego mówię, że zależy to częściowo od sytuacji. Być może inicjalizacja danych powinna pomijać setery, ale inny kod logiczny nie powinien. Wybierz odpowiednie reguły i trzymaj się tego.
Matt S
3
@DanielKaplan: chodzi o to, że wywoływanie programów pobierających i ustawiających jest w większości przypadków właściwą rzeczą i tylko w określonych sytuacjach nie. Jednak w kodzie świata rzeczywistego, jeśli zmienisz istniejącą implementację gettera / settera później, aby wprowadzić pewne zamierzone skutki uboczne, najprawdopodobniej będziesz musiał sprawdzić każde wywołanie gettera / settera w klasie, a także każdy bezpośredni dostęp do pole. Dlatego powinieneś starać się, aby twoje zajęcia były jak najmniejsze.
Doc Brown
33

Bezpośrednie wywołanie setera nie jest samo w sobie problemem. Pytanie powinno naprawdę brzmieć: Dlaczego nasz kod ma wszędzie setery?

Zmienne klasy są niebezpieczną grą, szczególnie tam, gdzie sama klasa zarządza własnym stanem. Zastanów się, ilu z tych seterów naprawdę musi istnieć. Ile można ustawić w konstruktorze, a następnie całkowicie zamknąć je sama klasa?

Jeśli pole musi być ustawiane zewnętrznie, zadaj sobie pytanie, czy powinna istnieć metoda z logiką. Czy sama klasa jest tak naprawdę klasą danych / stanu? Czy ta klasa ma więcej niż jedną odpowiedzialność?

Nie zrozum mnie źle, będą przypadki, w których będzie to w porządku. Nie mówię, że powinieneś całkowicie wyeliminować seterów na klasach z logiką. Ale powinny to być wyjątki, a nie reguła. I w tych kilku przypadkach powinno być oczywiste, do którego dostępu należy uzyskać bezpośredni dostęp.

pdr
źródło
1
Całkowicie się zgadzam / ćwiczę ten sposób myślenia. Myślę też, że poruszasz kwestię, która jest ważniejsza od mojego pytania. To powiedziawszy, nie wydaje mi się, żeby bezpośrednio odpowiadało na moje pierwotne pytanie. Chyba że mówisz: „w wielkim schemacie rzeczy twoje pytanie nie ma znaczenia”. Co może być również prawdą :)
Daniel Kaplan
@DanielKaplan: Dokładnie to mówię. :) Byłem rozdarty między odpowiedzią a komentarzem w tej, ale naprawdę nie czuję, że istnieje poprawna odpowiedź, która nie zadaje większego pytania.
pdr
9

Tak, metody twojej klasy powinny wywoływać osoby pobierające i ustawiające. Chodzi przede wszystkim o pisanie getterów i setterów na przyszłość. Możesz uczynić każdą właściwość polem i bezpośrednio ujawniać dane użytkownikom klasy. Powodem, dla którego budujesz programy pobierające i ustawiające, niekoniecznie jest to, że istnieje teraz złożona logika, ale aby interfejs nie zepsuł się w przyszłości, jeśli będziesz musiał go dodać.

Michael
źródło
1
Nie widzę związku między pierwszym zdaniem a resztą akapitu. Pierwsze zdanie mówi, że klasa powinna nazywać własnymi pobierającymi i ustawiającymi. W pozostałej części akapitu wyjaśniono, dlaczego kod klienta (tj. Kod korzystający z klasy) powinien używać funkcji pobierających i ustawiających zamiast bezpośredniego dostępu do pól. Ale ja nie wiem, jak to jest powód, klasa nie powinna bezpośrednio dostęp do jego własnych pól.
Daniel Kaplan
1
@DanielKaplan Chodzi mi o to, że przyczyny są takie same. Jeśli później dodasz logikę ustawiającą, wpływa ona na kod wewnętrzny tak samo (potencjalnie), jak zewnętrzny.
Michael
2
@Michael: Często mogą istnieć dobre powody, dla których klasa nie używa własnych pobierających / ustawiających. Jednym z powszechnych scenariuszy jest sytuacja, w której istnieje wiele ustawiaczy formularza. someField=newValue; refresh(); Jeśli metoda pozwala ustawić wiele pól, wywołanie ustawiaczy w celu zapisania tych pól spowodowałoby zbędne operacje „odświeżania”. Zapisanie wszystkich pól, a następnie refresh()jednorazowe wywołanie, może zapewnić bardziej wydajną i gładszą operację.
supercat
6

Aby odpowiedzieć na twoje pytania jednym słowem, tak.

Posiadanie klasy wywołującej własne moduły pobierające i ustawiające dodaje rozszerzalności i stanowi lepszą bazę dla przyszłego kodu.

Powiedz, że masz coś takiego:

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        this.year = year;
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}

Wywoływanie seterów na rok i wykonanie obecnie mogą nie dodawać żadnej funkcjonalności, ale co, jeśli chcesz dodać coś takiego jak sprawdzanie poprawności danych wejściowych do seterów?

public class Vehicle
{
    private int year;
    private String make;

    public Vehicle(int year, String make)
    {
        setYear(year);
        setMake(make);
    }

    public void setYear(int year)
    {
        if(year > 0)
        {
            this.year = year;
        }
        else
        {
            System.out.println(year + " is not a valid year!");
        }
    }

    public void setMake(String make)
    {
        this.make = make;
    }
}
Zach Latta
źródło
5

Jedną z rzeczy, o których nie wspomniano, jest to, że getter i settery (jak wszystkie metody) są wirtualne w Javie. Dodaje to kolejną funkcję do zawsze używania ich w kodzie. Inny użytkownik może rozszerzyć twoją klasę, nadpisując twoje pobierające i ustawiające. Klasa podstawowa używałaby wtedy danych podklasy zamiast własnych. W języku, w którym wyraźnie zaznaczasz funkcje wirtualne, jest to o wiele wygodniejsze, ponieważ możesz przewidzieć i zadeklarować, za pomocą których funkcji można to zrobić. W Javie jest to coś, o czym zawsze musisz wiedzieć. Jeśli jest to pożądane zachowanie, użycie ich w kodzie jest dobrą rzeczą, w przeciwnym razie nie tak bardzo.

Jonathan Henson
źródło
3

Jeśli próbujesz osiągnąć coś, co zapewnia interfejs publiczny, użyj getters / setters.

Jednak jako właściciel / programista klasy masz dostęp do prywatnych sekcji swojego kodu (oczywiście z klasy), ale jesteś również odpowiedzialny za złagodzenie niebezpieczeństw.

Więc może masz moduł pobierający, który iteruje po wewnętrznej liście i chcesz uzyskać bieżącą wartość bez zwiększania iteratora. W takim przypadku użyj zmiennej prywatnej.

public class MyClass
{
    private int i;
    private List<string> list;
    public string getNextString()
    {
        i++;
        return list[i];
    }

    private void getString()
    {
        // Do not increment
        string currentString = list[i];

        // Increment
        string nextString = getNextString();
    }
}
ConditionRacer
źródło
Czy możesz podać uzasadnienie?
Daniel Kaplan
1

Tak. Gettery i settery reprezentują stan, więc odwróć to pytanie - czy chcesz śledzić wiele sposobów zmiany stanu obiektów w ten sam sposób, chyba że musisz?

Im mniej rzeczy musisz śledzić, tym lepiej - dlatego łatwiej jest sobie poradzić z obiektami niezmiennymi.

Zastanów się, nic nie stoi na przeszkodzie, abyś miał zarówno pole publiczne, jak i publiczny getter / setter - ale co byś zyskał?

Czasami może być pożądane, a nawet konieczne, aby uzyskać bezpośredni dostęp do pola z jednego lub więcej powodów, nie powinieneś się tego unikać, jeśli tak się stanie. Ale tak naprawdę powinieneś to zrobić tylko wtedy, gdy jest to określone korzyści.

jmoreno
źródło
Chodzi o to, że pytam tylko o bezpośredni dostęp do pól w tej samej klasie. Rozumiem już cel programów pobierających i ustawiających kod klienta.
Daniel Kaplan
@DanielKaplan: Rozumiem to i mówię, że o ile to praktyczne, należy traktować je tak samo.
jmoreno
@DanielKaplan: Możesz mieć jednego setera prywatnego i jednego setera publicznego, które mają dokładnie taki sam wynik (wszystkie efekty uboczne są takie same) przynajmniej w przypadku momentu, ale co by to przyniosło inne korzyści niż możliwość rozbieżności? Różnica pomiędzy tym scenariuszu i co można opisać to, że nie można uniknąć w stanie uzyskać dostęp do stanu poza pobierające / ustawiające, ale można uniknąć faktycznie robić. Nie komplikuj kodu, chyba że musisz.
jmoreno
1

Obie metody mają swoje przypadki użycia. Ponieważ obiekt ustawiający publicznie utrzymuje spójność wartości pola (i / lub wartości powiązanych), należy używać narzędzia ustawiającego, gdy logika metody nie zakłóca tej logiki spójności. Jeśli po prostu ustawisz swoją „właściwość”, użyj setera. Z drugiej strony zdarzają się sytuacje, w których potrzebny jest bezpośredni dostęp do niektórych pól, na przykład operacje zbiorcze z ciężkim setera lub operacje, w których koncepcja setera jest zbyt prosta.

Twoim obowiązkiem jest utrzymanie spójności. Setter robi to z definicji, ale nie może obejmować skomplikowanych przypadków.

Artur
źródło
1

Powiedziałbym, że nie ..

Jeśli twoje programy pobierające / ustawiające po prostu pobierają i ustawiają (bez leniwej inicjalizacji, bez sprawdzania itp.), Po prostu używaj zmiennych. Jeśli w przyszłości zmienisz swoje metody pobierające / ustawiające, możesz bardzo dobrze zmienić swoją methodWithLogic()(ponieważ zmienia się Twoja klasa) i możesz zadzwonić do metody pobierającej / ustawiającej zamiast bezpośredniego przypisania. Możesz udokumentować to wywołanie dla gettera / settera (ponieważ dziwne będzie wywoływanie gettera / settera, gdy reszta kodu twojej klasy bezpośrednio używa zmiennej).

JVM będzie wykonywać częste połączenia do osób pobierających / ustawiających. Tak więc nigdy nie jest to problem z wydajnością. Korzyści ze stosowania zmiennych to czytelność i IMHO, wybrałbym to.

Mam nadzieję że to pomoże..

Pravin Sonawane
źródło