Używanie publicznego finału zamiast prywatnych pobierających

51

Widzę najbardziej niezmienne POJO napisane w ten sposób:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

Mimo to mam tendencję do pisania ich w ten sposób:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Zauważ, że odniesienia są ostateczne, więc Obiekt jest nadal niezmienny. Pozwala mi pisać mniej kodu i umożliwia krótszy (o 5 znaków: dostęp geti ()).

Jedyną wadą, jaką widzę, jest to, że jeśli chcesz zmienić implementację w getFoo()dół drogi, aby zrobić coś szalonego, nie możesz. Ale realistycznie to się nie zdarza, ponieważ Obiekt jest niezmienny; możesz zweryfikować podczas tworzenia instancji, tworzyć niezmienne kopie obronne podczas tworzenia instancji (patrz ImmutableListna przykład Guava ) i przygotować obiekty foolub barobiekty na getwezwanie.

Czy brakuje mi wad?

EDYTOWAĆ

Przypuszczam, że kolejną wadą, której mi brakuje, są biblioteki serializacji wykorzystujące refleksję nad metodami zaczynającymi się od getlub is, ale to dość okropna praktyka ...

Cory Kendall
źródło
Co do cholery? finalnie czyni zmiennej niezmienną. Zwykle używam projektu, w którym definiuję finalpola przed utworzeniem wywołania zwrotnego, aby wywołanie zwrotne mogło uzyskać dostęp do tych pól. Może oczywiście wywoływać wszystkie metody, w tym dowolne setX.
Tomáš Zato,
@ TomášZato Brak metody setX na String, intlub MyObject. W pierwszej wersji finalma jedynie zapewnić, że metody inne niż konstruktor w klasie nie będą próbować bar = 7;na przykład. W drugiej wersji, finaljest konieczne, aby zapobiec konsumentów robi: MyObject x = new MyObject("hi", 5); x.bar = 7;.
Cory Kendall,
1
Cóż, „ Zwróć uwagę, że odniesienia są ostateczne, więc Objectnadal są niezmienne. mylące - w ten sposób wydaje się, że uważasz, że każdy final Objectjest niezmienny, a tak nie jest. Przepraszam za nieporozumienie.
Tomáš Zato,
3
Gdyby wartości leżące u podstaw były zmienne, droga prywatna z akcesoriami też by tego nie powstrzymała - myObj.getFoo().setFrob(...).
Beni Cherniavsky-Paskin

Odpowiedzi:

38

Cztery wady, o których mogę myśleć:

  1. Jeśli chcesz mieć tę samą jednostkę tylko do odczytu i można ją modyfikować, wspólnym wzorcem jest posiadanie niezmiennej klasy encji, która udostępnia tylko akcesory z chronionymi zmiennymi składowymi, a następnie utwórz MutableEntity, który ją rozszerza i dodaje ustawiające. Twoja wersja temu zapobiega.
  2. Korzystanie z metod pobierających i ustawiających jest zgodne z konwencją JavaBeans. Jeśli chcesz wykorzystać swoją klasę jako fasolę w technologiach opartych na właściwościach, takich jak JSTL lub EL, musisz ujawnić publiczne programy pobierające.
  3. Jeśli kiedykolwiek chcesz zmienić implementację, aby uzyskać wartości lub sprawdzić je w bazie danych, musisz ponownie kodować kod klienta. Podejście typu akcesor / mutator umożliwia jedynie zmianę implementacji.
  4. Najmniej zdumienie - kiedy widzę zmienne instancji publicznej, od razu szukam, kto może je mutować i martwię się, że otwieram pudełko pandory, ponieważ utracono enkapsulację. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

To powiedziawszy, twoja wersja jest zdecydowanie bardziej zwięzła. Gdyby to była klasa specjalistyczna, która jest używana tylko w ramach określonego pakietu (być może zakres pakietu jest tutaj dobrym pomysłem), to mogę rozważyć to jednorazowo. Ale nie ujawniłbym takich głównych API.

Brandon
źródło
3
Świetne odpowiedzi; Nie zgadzam się z niektórymi z nich, ale nie jestem pewien, czy ta strona jest najlepszym forum do dyskusji. W skrócie: 1) Mogę łamać innych, stosując zmienną formę, w której zakładają, że jest niezmienna (być może powinienem dodać finał do klasy w moich przykładach), 2) Zgadzam się, 3) Argumentowałbym, że nie jest już POJO w tym przypadek 4) Na razie się zgadzam, ale mam nadzieję, że takie dyskusje mogą zmienić to, co najmniej zadziwiające :).
Cory Kendall
2
5) Nie można bezpiecznie ujawniać klas / typów z natury zmiennych, takich jak tablice. Bezpiecznym sposobem jest za każdym razem zwracanie kopii z gettera.
Clockwork-Muse
@ Clockwork-Muse możesz, jeśli zakładasz, że ludzie nie są idiotami. Podczas uzyskiwania dostępu do someObj.thisList.add () oczywiste jest, że modyfikujesz stan niektórych innych obiektów. Z someObj.getThisList (). Add () to nieznane. Musisz zapytać o dokumentację lub poszukać źródła, ale na podstawie samej deklaracji metody nie można powiedzieć.
kritzikratzi
2
@kritzikratzi - Problem polega na tym, że nie możesz zagwarantować, że Twój kod nie wpadnie w idiotów. W pewnym momencie tak się stanie (a może nawet stanie się sobą!), A niespodzianka może być ogromnym problemem.
Clockwork-Muse
1
Cóż, pozwolenie, aby każdy zmienny typ dziedziczył z niezmiennego typu, jest z natury niewłaściwy. Pamiętaj, że niezmienny typ daje gwarancję „Nie zmieniam się. Nigdy”.
Deduplicator
26

Pozbądź się również pobierających / ustawiających i wszystko w porządku!

Jest to bardzo kontrowersyjny temat wśród programistów Java.

W każdym razie istnieją dwie sytuacje, w których używam zmiennych publicznych zamiast (!) Getters / setters:

  1. publiczny finał To dla mnie oznacza, że ​​„jestem niezmienny” o wiele lepiej niż po prostu getter. Większość IDE wskaże ostateczny modyfikator z literą „F” podczas automatycznego uzupełniania. W przeciwieństwie do getters / setters, w których musisz szukać nieobecności setXXX.
  2. public non-final Uwielbiam to dla klas danych. Po prostu ujawniam publicznie wszystkie pola. Żadnych pobierających, ustawiających, konstruktorów. Nie nic. Mniej niż pojo. Dla mnie to natychmiast sygnalizuje „słuchaj, jestem głupi. Trzymam dane, to wszystko. Twoim zadaniem jest umieszczenie we mnie właściwych danych”. Gson / JAXB / itp. poradzić sobie z tymi klasami w porządku. Są błogosławieństwem do napisania. Nie ma wątpliwości co do ich celu lub możliwości. A co najważniejsze: wiesz, że po zmianie zmiennej nie występują żadne skutki uboczne. IMHO powoduje to bardzo zwięzłe modele danych z kilkoma niejednoznacznościami, podczas gdy osoby pobierające i ustawiające mają ten ogromny problem, w którym czasami dzieje się w nich magia.
kritzikratzi
źródło
5
Miło jest widzieć trochę zdrowia psychicznego raz na jakiś czas :)
Navin
2
Aż nadejdzie dzień, w którym ewoluuje Twój model, a jedno ze starych pól można teraz wyprowadzić z drugiego. Ponieważ chcesz zachować zgodność z poprzednimi wersjami, stare pole musi pozostać, ale zamiast po prostu zmieniać kod pobierający i ustawiający, musisz zmienić wszędzie tam, gdzie to stare pole było używane, aby upewnić się, że jest zgodne z nową reprezentacją modelu. Łatwo napisać ... tak, na pewno ... koszmar do utrzymania na dłuższą metę ... Właśnie dlatego mamy getter / setter lub jeszcze lepiej w akcesoriach C # własności.
Newtopian
9

Słowami laika:

  • Naruszasz enkapsulację , aby zapisać kilka wierszy kodu. To przeczy celowi OOD.
  • Kod klienta będzie sztywno powiązany z nazwiskami członków twojej klasy. Sprzężenie jest złe. Głównym celem OOD jest zapobieganie sprzężeniu.
  • Jesteś również bardzo pewien, że twoja klasa nigdy nie będzie musiała być zmienna. Rzeczy się zmieniają. Zmiana jest jedyną stałą rzeczą.
Tulains Córdova
źródło
6
Jak to jest naruszenie enkapsulacji? Obie wersje są tak samo narażone na świat zewnętrzny (z dostępem tylko do odczytu). Masz rację co do twardego sprzężenia i nieelastyczności zmiany, ale nie będę tego argumentować.
KChaloux
4
@KChaloux Mylisz „naruszenie enkapsulacji” z „kodem, który nie skompilował się i nie musisz tracić czasu na jego naprawianie”. Drugi wydarzy się później. Żeby było jasne: enkapsulacja jest już naruszona w teraźniejszości. Wnętrza twojej klasy nie są już zamknięte w teraźniejszości. Ale kod klienta ulegnie awarii w przyszłości, jeśli w przyszłości zmienią się klasy, ponieważ hermetyzacja była zepsuta w przeszłości. Istnieją dwie różne „przerwy”, dzisiaj enkapsulacja, jutro kod klienta, z powodu braku enkapsulacji.
Tulains Córdova
8
Technicznie, kiedy używasz getters / etc. zamiast tego kod klienta będzie sztywno powiązany z nazwami metod. Spójrz, ile bibliotek musi zawierać starsze wersje metod ze znacznikiem / adnotacją „przestarzałe”, ponieważ starszy kod jest nadal od nich zależny (a niektórzy ludzie mogą nadal z nich korzystać, ponieważ uważają, że łatwiej z nimi pracować, ale nie jesteś powinien to zrobić).
JAB
5
Pragmatyczny: jak często programiści faktycznie refaktoryzują prywatne nazwy pól bez zmiany nazw publicznych pobierających / ustawiających? Z tego, co mogę powiedzieć, istnieje ogólnokrajowa konwencja, aby nazwać je tak samo, nawet jeśli konwencja ta może wynikać jedynie z narzędzi IDE, które generują metody pobierające i ustawiające z nazw pól. Teoretyczny: jednak z perspektywy modelowania obiektowego wszystko, co publiczne, jest częścią zamówienia publicznego, a nie wewnętrznym szczegółem wdrożenia. Więc jeśli ktoś zdecyduje się na ostateczną opinię publiczną pola, nie są one mówiąc, że to jest umowa obiecują spełnić?
Thomas Jung,
3
@ user949300 Powiedz mi, które z nich będę mógł się uczyć.
Tulains Córdova
6

Jedną z możliwych wad, którą widzę od razu, jest to, że jesteś przywiązany do wewnętrznej reprezentacji danych w klasie. To chyba nie jest wielka sprawa, ale jeśli użyjesz seterów i zdecydujesz, że foo i bar zostaną zwrócone z innej klasy zdefiniowanej gdzie indziej, klasy zużywające MyObject nie będą musiały się zmieniać. Wystarczy dotknąć MyObject. Jeśli jednak użyjesz nagich wartości, musisz dotknąć wszędzie tego, z którego korzystałem MyObject.

Ipaul
źródło