Jaki styl jest lepszy (zmienna instancji vs. wartość zwracana) w Javie

32

Często próbuję zdecydować, który z tych dwóch sposobów użyć, gdy muszę użyć wspólnych danych w niektórych metodach w moich klasach. Jaki byłby lepszy wybór?

W tej opcji mogę utworzyć zmienną instancji, aby uniknąć konieczności deklarowania dodatkowych zmiennych, a także aby uniknąć definiowania parametrów metody, ale może nie być tak jasne, gdzie te zmienne są tworzone / modyfikowane:

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

A może po prostu tworzysz zmienne lokalne i przekazujesz je jako argumenty?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

Jeśli odpowiedź brzmi, że oba są poprawne, który z nich jest widziany / używany częściej? Również, jeśli możesz podać dodatkowe zalety / wady dla każdej opcji, byłoby bardzo cenne.

carlossierra
źródło
1
Reletad (duplikat?): Programmers.stackexchange.com/questions/164347/…
Martin Ba
1
Nie sądzę, aby ktokolwiek zauważył, że umieszczanie wyników pośrednich w zmiennych instancji może utrudnić współbieżność ze względu na możliwość rywalizacji między wątkami dla tych zmiennych.
sdenham 24.04.16

Odpowiedzi:

107

Dziwię się, że o tym jeszcze nie wspominałem ...

To zależy od tego, czy var1jest faktycznie częścią Twojego obiektu stanu .

Zakładasz, że oba te podejścia są poprawne i że to tylko kwestia stylu. Mylisz się.

Chodzi wyłącznie o to, jak prawidłowo modelować.

Podobnie privateistnieją metody instancji służące do mutowania stanu obiektu . Jeśli to nie jest twoja metoda, powinno być private static.

MetaFight
źródło
7
@mucaho: transientnie ma z tym nic wspólnego, ponieważ transientdotyczy stanu trwałego , jak w częściach obiektu, które są zapisywane, gdy robisz coś takiego jak serializacja obiektu. Na przykład magazyn zaplecza ArrayList jest transientnawet niezwykle istotny dla stanu ArrayList, ponieważ podczas szeregowania ArrayList chcesz zapisać tylko tę część magazynu, która zawiera rzeczywiste elementy ArrayList, a nie wolne miejsce na końcu zarezerwowane dla dalszych uzupełnień elementów.
użytkownik2357112 obsługuje Monikę
4
Dodanie do odpowiedzi - jeśli var1jest to potrzebne dla kilku metod, ale nie jest częścią stanu MyClass, może być czas na umieszczenie var1tych metod w innej klasie, z której będą korzystać MyClass.
Mike Partridge
5
@CandiedOrange Mówimy tutaj o poprawnym modelowaniu. Kiedy mówimy „część stanu obiektu”, nie mówimy o dosłownych fragmentach kodu. Omawiamy koncepcyjne pojęcie stanu, jaki powinien być stan obiektu, aby poprawnie modelować pojęcia. „Żywotny okres użytkowania” jest prawdopodobnie największym czynnikiem decydującym w tym zakresie.
jpmc26
4
@CandiedOrange Next i Previous to oczywiście metody publiczne. Jeśli wartość var1 jest istotna tylko w ciągu szeregu metod prywatnych, to wyraźnie nie jest częścią trwałego stanu obiektu i dlatego powinna być przekazana jako argument.
Taemyr
2
@CandiedOrange Po dwóch komentarzach wyjaśniłeś, co masz na myśli. Mówię, że łatwo mógłbyś mieć w pierwszej odpowiedzi. Poza tym tak, zapomniałem, że jest coś więcej niż tylko przedmiot; Zgadzam się, że czasami metody instancji prywatnych mają swój cel. Po prostu nie mogłem sobie przypomnieć. EDYCJA: Właśnie zdałem sobie sprawę, że tak naprawdę nie jesteś pierwszą osobą, która mi odpowiedziała. Mój błąd. Byłem zdezorientowany, dlaczego jedna odpowiedź była zwięzła, jedno zdanie, brak odpowiedzi, podczas gdy następna faktycznie wyjaśniała.
Pozew Fund Moniki w dniu
17

Nie wiem, co jest bardziej rozpowszechnione, ale zawsze robiłbym to drugie. Bardziej wyraźnie komunikuje przepływ danych i czas życia, i nie powoduje wzdęcia każdej instancji twojej klasy w polu, którego jedyny istotny czas życia występuje podczas inicjalizacji. Powiedziałbym, że to pierwsze jest mylące i znacznie utrudnia przegląd kodu, ponieważ muszę rozważyć możliwość modyfikacji dowolnej metody var1.

Derek Elkins
źródło
7

Powinieneś ograniczyć zakres swoich zmiennych tak bardzo, jak to możliwe (i rozsądne). Nie tylko w metodach, ale ogólnie.

Twoje pytanie oznacza, że ​​zależy to od tego, czy zmienna jest częścią stanu obiektu. Jeśli tak, można używać go w tym zakresie, tj. Całego obiektu. W takim przypadku wybierz pierwszą opcję. Jeśli nie, przejdź do drugiej opcji, ponieważ zmniejsza widoczność zmiennych, a tym samym ogólną złożoność.

Andy
źródło
5

Jaki styl jest lepszy (zmienna instancji vs. wartość zwracana) w Javie

Jest inny styl - użyj kontekstu / stanu.

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

Podejście to ma wiele zalet. Obiekty stanu mogą zmieniać się na przykład niezależnie od obiektu, dając dużo miejsca na przyszłość.

Jest to wzorzec, który działa również dobrze w systemie rozproszonym / serwerowym, w którym niektóre szczegóły muszą być zachowane podczas połączeń. Możesz przechowywać dane użytkownika, połączenia z bazą danych itp. W stateobiekcie.

OldCurmudgeon
źródło
3

Chodzi o skutki uboczne.

Pytanie o var1to, czy jest częścią państwa, nie ma sensu tego pytania. Jasne, jeśli var1musi się utrzymywać, musi to być instancja. Można zastosować dowolne podejście, niezależnie od tego, czy konieczne jest wytrwałość.

Podejście niepożądane

Niektóre zmienne instancji są używane tylko do komunikacji między prywatnymi metodami od wywołania do wywołania. Tego rodzaju zmienną instancji można zrefaktoryzować, ale nie musi tak być. Czasami rzeczy są z nimi wyraźniejsze. Ale nie jest to pozbawione ryzyka.

Wypuszczasz zmienną z jej zakresu, ponieważ jest ona używana w dwóch różnych zakresach prywatnych. Nie dlatego, że jest potrzebny w zakresie, w którym go umieszczasz. To może być mylące. „Globale są złe!” poziom dezorientacji. To może działać, ale po prostu nie będzie dobrze skalować. Działa tylko w małych. Żadnych dużych przedmiotów. Bez długich łańcuchów spadkowych. Nie powoduj efektu jo-jo .

Podejście funkcjonalne

Teraz, nawet jeśli var1musi się utrzymywać, nic nie mówi, że musisz użyć, jeśli dla każdej przejściowej wartości, którą może przyjąć, zanim osiągnie stan, który chcesz zachować między połączeniami publicznymi. Oznacza to, że nadal możesz ustawić var1instancję, używając jedynie funkcjonalnych metod.

Więc część stanu, czy nie, nadal możesz zastosować jedno z dwóch podejść.

W tych przykładach „var1” jest tak enkapsulowane, że oprócz tego, że debugger wie, że istnieje. Zgaduję, że zrobiłeś to celowo, ponieważ nie chcesz nas uprzedzić. Na szczęście nie obchodzi mnie które.

Ryzyko skutków ubocznych

To powiedziawszy, wiem skąd pochodzi twoje pytanie. Pracowałam pod nędznym yo yo „ing dziedziczenie że mutuje zmiennej instancji na wielu poziomach w różnych metod i znika squirrelly próbując go śledzić. To jest ryzyko.

To ból popycha mnie do bardziej funkcjonalnego podejścia. Metoda może udokumentować swoje zależności i dane wyjściowe w sygnaturze. To potężne, jasne podejście. Pozwala także zmienić to, co przekazujesz prywatną metodą, dzięki czemu jest łatwiejsza do wykorzystania w klasie.

Pozytywne skutki uboczne

To także ogranicza. Czyste funkcje nie mają skutków ubocznych. To może być dobra rzecz, ale nie jest zorientowana obiektowo. Dużą częścią orientacji obiektowej jest możliwość odniesienia się do kontekstu poza metodą. Robienie tego bez przeciekania globali tutaj i po nich jest siłą OOP. Mam elastyczność globalną, ale jest ładnie zawarta w klasie. Mogę wywołać jedną metodę i mutować każdą zmienną instancji na raz, jeśli chcę. Jeśli to zrobię, jestem zobowiązany przynajmniej podać nazwę metody, która wyjaśnia, co się dzieje, aby ludzie nie byli zaskoczeni, gdy to się stanie. Komentarze również mogą pomóc. Czasami te komentarze są sformalizowane jako „warunki postowe”.

Minusem funkcjonalnych metod prywatnych

Podejście funkcjonalne wyjaśnia niektóre zależności. Chyba że jesteś w czysto funkcjonalnym języku, nie może wykluczyć ukrytych zależności. Nie wiesz, patrząc tylko na sygnaturę metod, że nie ukrywa to przed tobą efektu ubocznego w pozostałej części kodu. Po prostu nie.

Post warunkowe

Jeśli ty i wszyscy inni w zespole niezawodnie udokumentuje skutki uboczne (warunki przed / po) w komentarzach, zysk z podejścia funkcjonalnego jest znacznie mniejszy. Tak, wiem, śnij dalej.

Wniosek

Osobiście wolę funkcjonalne metody prywatne w obu przypadkach, jeśli mogę, ale szczerze mówiąc jest to głównie dlatego, że te komentarze warunkowe przed / po warunkowe efekty uboczne nie powodują błędów kompilatora, gdy są przestarzałe lub gdy metody są wywoływane w niewłaściwym porządku. Chyba że naprawdę potrzebuję elastyczności efektów ubocznych, wolałbym tylko wiedzieć, że coś działa.

MagicWindow
źródło
1
„Niektóre zmienne instancji są używane tylko do komunikacji między prywatnymi metodami od połączenia do połączenia”. To zapach kodu. Gdy zmienne instancji są używane w ten sposób, jest to znak, że klasa jest za duża. Wyodrębnij te zmienne i metody do nowej klasy.
kevin cline
2
To naprawdę nie ma sensu. Chociaż OP mógłby napisać kod w paradygmacie funkcjonalnym (i byłoby to na ogół dobrą rzeczą), oczywiście nie jest to kontekst pytania. Próba powiedzenia OP, że mogą uniknąć zapisywania stanu obiektu przez zmianę paradygmatów, nie jest tak naprawdę istotna ...
jpmc26,
@kevincline Przykład OP ma tylko jedną zmienną instancji i 3 metody. Zasadniczo został już wyodrębniony do nowej klasy. Nie chodzi o to, że przykład robi coś przydatnego. Wielkość klasy nie ma nic wspólnego z tym, jak twoje prywatne metody pomocnika komunikują się ze sobą.
MagicWindow
@ jpmc26 OP pokazał już, że mogą uniknąć przechowywania var1jako zmiennej stanu przez zmianę paradygmatów. Zakres klasy NIE jest tylko miejscem przechowywania stanu. Jest to również zakres obejmujący. Oznacza to, że istnieją dwie możliwe motywacje, aby umieścić zmienną na poziomie klasy. Możesz twierdzić, że robisz to tylko dla ograniczonego zakresu, a państwo nie jest złe, ale mówię, że jest to kompromis z arią, która jest również zła. Wiem to, ponieważ musiałem utrzymywać kod, który to robi. Niektóre zostały zrobione dobrze. Niektóre były koszmarem. Linia między nimi nie jest stanem. To czytelność.
MagicWindow 22.04.16
Reguła stanu poprawia czytelność: 1) unikaj tymczasowych wyników operacji wyglądających jak stan 2) unikaj ukrywania zależności metod. Jeśli arsenał metody jest wysoki, to albo nieredukowalnie i autentycznie reprezentuje złożoność tej metody, albo obecny projekt ma niepotrzebną złożoność.
sdenham 24.04.16
1

Pierwszy wariant wygląda na nieintuicyjny i potencjalnie niebezpieczny dla mnie (wyobraź sobie, z jakiegokolwiek powodu ktoś upublicznia twoją prywatną metodę).

Wolałbym raczej utworzyć twoje zmienne po zbudowaniu klasy lub przekazać je jako argumenty. Ten ostatni daje możliwość używania funkcjonalnych idiomów i nie polegania na stanie obiektu zawierającego.

Brian Agnew
źródło
0

Istnieją już odpowiedzi mówiące o stanie obiektu i kiedy preferowana jest druga metoda. Chcę tylko dodać jeden typowy przypadek użycia dla pierwszego wzoru.

Pierwszy wzorzec jest całkowicie poprawny, gdy wszystko, co robi twoja klasa, polega na tym, że zawiera on algorytm . Jednym z przykładów użycia jest to, że jeśli napisałeś algorytm do jednej metody, byłby on zbyt duży. Więc dzielisz go na mniejsze metody, czynisz z niego klasę, a metody podrzędne są prywatne.

Przekazywanie całego stanu algorytmu przez parametry może być uciążliwe, dlatego korzystasz z prywatnych pól. Jest to również zgodne z regułą z pierwszego akapitu, ponieważ jest to zasadniczo stan instancji. Musisz tylko pamiętać i odpowiednio udokumentować, że algorytm nie jest ponownie wysyłany, jeśli użyjesz do tego prywatnych pól . To nie powinno być problemem przez większość czasu, ale może cię ugryźć.

Honza Brabec
źródło
0

Spróbujmy przykładu, który coś robi. Wybacz mi, ponieważ to jest javascript, a nie Java. Punkt powinien być taki sam.

Odwiedź https://blockly-games.appspot.com/pond-duck?lang=en , kliknij kartę javascript i wklej to:

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

Należy zauważyć, że dir, widi disnie są coraz przeszedł wokół partii. Należy również zauważyć, że kod w funkcji, która lock()zwraca, przypomina pseudo kod. To jest rzeczywisty kod. Ale jest bardzo łatwy do odczytania. Możemy dodać przekazywanie i przypisywanie, ale dodałoby to bałaganu, którego nigdy nie zobaczysz w pseudo-kodzie.

Jeśli chcesz argumentować, że nie wykonanie przypisania i przekazania jest w porządku, ponieważ te trzy zmienne są stanem trwałym, rozważ przeprojektowanie, które przypisuje dirlosową wartość każdej pętli. To nie jest teraz trwałe, prawda?

Jasne, teraz moglibyśmy teraz zmniejszyć dirzakres, ale nie bez konieczności zaśmiecania naszego pseudo-podobnego kodu przekazywaniem i ustawianiem.

Więc nie, stan nie jest powodem, dla którego decydujesz się użyć lub nie użyć efektów ubocznych zamiast przejść i powrócić. Same skutki uboczne nie oznaczają również, że Twój kod jest nieczytelny. Nie zyskujesz korzyści z czystego kodu funkcjonalnego . Ale dobrze zrobione, z dobrymi nazwiskami, faktycznie mogą być przyjemne do czytania.

Nie oznacza to, że nie mogą zmienić się w spaghetti jak koszmar. Ale co nie może?

Miłego polowania na kaczki.

candied_orange
źródło
1
Funkcje wewnętrzne / zewnętrzne są innym paradygmatem niż to, co opisuje OP. - Zgadzam się, że kompromis między zmiennymi lokalnymi w funkcji zewnętrznej przeciwnie przekazywanymi argumentami do funkcji wewnętrznych jest podobny do zmiennych prywatnych w klasie przeciwnie przekazywanych argumentów do funkcji prywatnych. Jeśli jednak wykonasz to porównanie, czas życia obiektu będzie odpowiadał jednemu uruchomieniu funkcji, więc zmienne w funkcji zewnętrznej są częścią trwałego stanu.
Taemyr
@Taemyr OP opisuje prywatne metody wywoływane w publicznym konstruktorze, który wydaje mi się bardzo podobny do wewnętrznego w zewnętrznym. Stan nie jest tak naprawdę „trwały”, jeśli nadepniesz na niego przy każdym wejściu do funkcji. Czasami stan jest używany jako miejsce wspólne, w którym metody mogą pisać i czytać. To nie znaczy, że trzeba to utrwalić. Fakt, że i tak się utrzymuje, nie jest czymś, na czym MUSI zależeć.
candied_orange 22.04.16
1
Jest trwały, nawet jeśli nadepniesz na niego za każdym razem, gdy pozbywasz się obiektu.
Taemyr
1
Dobre strony, ale wyrażenie ich w JavaScript naprawdę zaciemnia dyskusję. Ponadto, jeśli usuniesz składnię JS, skończysz na tym, że obiekt kontekstu zostanie przekazany (zamknięcie funkcji zewnętrznej)
fdreger
1
@sdenham Twierdzę, że istnieje różnica między atrybutami klasy a przejściowymi pośrednimi wynikami operacji. Nie są równoważne. Ale jedno może być przechowywane w drugim. Z pewnością nie musi tak być. Pytanie brzmi, czy powinno być. Tak, są problemy semantyczne i dotyczące życia. Istnieją również problemy z dobrodziejstwem. Nie uważasz, że są wystarczająco ważne. W porządku. Ale powiedzenie, że użycie poziomu klasy do czegoś innego niż stan obiektu jest niepoprawnym modelowaniem, wymaga nalegania na jeden sposób modelowania. Utrzymałem profesjonalny kod, który zrobił to w drugą stronę.
candied_orange 28.04.16