Che stile è migliore (variabile di istanza e valore di ritorno) in Java

31

Spesso mi trovo a dover lottare per decidere quale di questi due modi usare quando ho bisogno di usare dati comuni attraverso alcuni metodi nelle mie classi. Quale sarebbe una scelta migliore?

In questa opzione, posso creare una variabile di istanza per evitare la necessità di dover dichiarare variabili aggiuntive e anche per evitare di definire i parametri del metodo, ma potrebbe non essere così chiaro dove tali variabili vengono istanziate / modificate:

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;
    }
}

O semplicemente istanziando variabili locali e passandole come argomenti?

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;
    }
}

Se la risposta è che sono entrambi corretti, quale viene visto / usato più spesso? Inoltre, se puoi fornire ulteriori vantaggi / svantaggi per ciascuna opzione, sarebbe molto utile.

    
posta carlossierra 21.04.2016 - 17:18
fonte

8 risposte

107

Sono sorpreso che questo non sia ancora menzionato ...

Dipende se var1 fa effettivamente parte dello stato dell'oggetto

del tuo oggetto.

Supponi che entrambi questi approcci siano corretti e che sia solo una questione di stile. Hai torto.

Questo è interamente su come modellare correttamente.

Allo stesso modo, esistono metodi di istanza private per mutare lo stato dell'oggetto . Se questo non è ciò che sta facendo il tuo metodo, dovrebbe essere private static .

    
risposta data 21.04.2016 - 19:17
fonte
17

Non so quale sia più diffuso, ma farei sempre il secondo. Comunica in modo più chiaro il flusso di dati e la durata, e non gonfia ogni istanza della tua classe con un campo la cui unica durata rilevante è durante l'inizializzazione. Direi che il primo è solo confuso e rende la revisione del codice molto più difficile, perché devo considerare la possibilità che qualsiasi metodo possa modificare var1 .

    
risposta data 21.04.2016 - 17:37
fonte
7

Dovresti ridurre il ambito delle tue variabili il più possibile (e ragionevole). Non solo nei metodi, ma in generale.

Per la tua domanda questo significa che dipende se la variabile fa parte dello stato dell'oggetto. Se sì, va bene usarlo in quell'ambito, cioè l'intero oggetto. In questo caso, vai con la prima opzione. Se no, vai con la seconda opzione in quanto riduce la visibilità delle variabili e quindi la complessità complessiva.

    
risposta data 21.04.2016 - 20:39
fonte
5

What Style is Better (Instance Variable vs. Return Value) in Java

C'è un altro stile: usa un contesto / stato.

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;
    }
}

Ci sono molti vantaggi in questo approccio. Ad esempio, gli oggetti di stato possono cambiare indipendentemente dall'oggetto, offrendo molto spazio per il futuro.

Questo è un pattern che funziona bene anche in un sistema distribuito / server in cui alcuni dettagli devono essere preservati attraverso le chiamate. Puoi memorizzare i dettagli dell'utente, le connessioni al database, ecc. Nell'oggetto state .

    
risposta data 22.04.2016 - 12:57
fonte
3

Riguarda gli effetti collaterali.

Chiedendo se var1 fa parte dello stato, manca il punto di questa domanda. Certo se var1 deve persistere, deve essere un'istanza. Entrambi gli approcci possono essere fatti per lavorare indipendentemente dal fatto che la persistenza sia necessaria o meno.

L'approccio agli effetti collaterali

Alcune variabili di istanza sono utilizzate solo per comunicare tra metodi privati da una chiamata all'altra. Questo tipo di variabile di istanza può essere ridefinita al di fuori dell'esistenza ma non deve esserlo. A volte le cose sono più chiare con loro. Ma questo non è senza rischi.

Stai lasciando una variabile fuori dal suo ambito perché è usata in due diversi ambiti privati. Non perché sia necessario nel campo di applicazione, lo stai posizionando. Questo può essere fonte di confusione. I "globali sono malvagi!" livello di confusione. Questo può funzionare ma non si ridimensionerà. Funziona solo nel piccolo. Niente grandi oggetti Nessuna catena di eredità lunga. Non provocare un effetto yo yo .

L'approccio funzionale

Ora, anche se var1 deve persistere, nulla dice che devi usare se per ogni valore transitorio che potrebbe assumere prima che raggiunga lo stato che vuoi conservare tra le chiamate pubbliche. Ciò significa che puoi ancora impostare un'istanza var1 utilizzando solo metodi più funzionali.

Quindi parte dello stato o no, puoi comunque utilizzare entrambi gli approcci.

In questi esempi 'var1' è così incapsulato che nulla, a parte il tuo debugger, sa che esiste. Immagino che tu l'abbia fatto deliberatamente perché non vuoi influenzarci. Fortunatamente non mi interessa quale.

Il rischio di effetti collaterali

Detto questo, so da dove viene la tua domanda. Ho lavorato sotto miserabile yo yo all'ereditarietà che muta una variabile di istanza a più livelli in più metodi ed è andata squirrelly cercando di seguirlo. Questo è il rischio.

Questo è il dolore che mi spinge ad un approccio più funzionale. Un metodo può documentare le sue dipendenze e produrre nella sua firma. Questo è un approccio potente e chiaro. Permette anche di cambiare ciò che si passa al metodo privato rendendolo più riusabile all'interno della classe.

Il lato positivo degli effetti collaterali

È anche limitante. Le funzioni pure non hanno effetti collaterali. Questa può essere una buona cosa ma non è orientata agli oggetti. Una grande parte dell'orientamento agli oggetti è la capacità di riferirsi a un contesto esterno al metodo. Fare ciò senza far fuoriuscire globalmente tutto il mondo qui e via è la forza di OOP. Ottengo la flessibilità di un globale ma è ben contenuto nella classe. Posso chiamare un metodo e mutare ogni variabile di istanza in una volta, se mi piace. Se lo faccio, sono obbligato a dare almeno al metodo un nome che chiarisca a cosa serve, così le persone non saranno sorprese quando ciò accadrà. I commenti possono aiutare pure. A volte questi commenti sono formalizzati come "post conditions".

Lo svantaggio dei metodi privati funzionali

L'approccio funzionale rende chiare le dipendenze alcune . A meno che non sia in un puro linguaggio funzionale, non può escludere dipendenze nascoste. Non sai, guardando solo la firma di un metodo, che non nasconde un effetto collaterale da te nel resto del codice. Semplicemente no.

Post conditionals

Se tu e tutti gli altri membri del team documentate in modo affidabile gli effetti collaterali (condizioni pre / post) nei commenti, il guadagno dall'approccio funzionale è molto inferiore. Sì, lo so, onora.

Conclusione

Personalmente tendo a privilegiare metodi privati funzionali in entrambi i casi, se posso, ma onestamente è soprattutto perché quei commenti sugli effetti collaterali pre / post condizionali non causano errori del compilatore quando sono obsoleti o quando i metodi vengono chiamati fuori servizio. A meno che non abbia davvero bisogno della flessibilità degli effetti collaterali, preferirei semplicemente sapere che le cose funzionano.

    
risposta data 21.04.2016 - 22:58
fonte
1

La prima variante sembra non intuitiva e potenzialmente pericolosa per me (immagina per qualunque motivo qualcuno renda pubblico il tuo metodo privato).

Preferirei piuttosto istanziare le tue variabili sulla costruzione della classe, o passarle come argomenti. Quest'ultimo ti dà la possibilità di usare idiomi funzionali e non fare affidamento sullo stato dell'oggetto contenitore.

    
risposta data 21.04.2016 - 18:50
fonte
0

Esistono già risposte che parlano dello stato dell'oggetto e quando viene preferito il secondo metodo. Voglio solo aggiungere un caso d'uso comune per il primo modello.

Il primo modello è perfettamente valido quando tutto ciò che fa la tua classe è che incapsula un algoritmo . Un caso d'uso per questo è che se hai scritto l'algoritmo su un singolo metodo sarebbe troppo grande. Quindi lo suddividi in metodi più piccoli rendilo una classe e rendi privati i metodi sub.

Ora passare tutto lo stato dell'algoritmo tramite parametri potrebbe diventare noioso, quindi utilizzi i campi privati. È anche in accordo con la regola del primo paragrafo perché è fondamentalmente uno stato dell'istanza. Basta tenere a mente e documentare correttamente che l'algoritmo non è rientranti se si usano campi privati per questo . Questo non dovrebbe essere un problema per la maggior parte del tempo, ma potrebbe darti un morso.

    
risposta data 22.04.2016 - 10:32
fonte
0

Proviamo un esempio che fa qualcosa. Perdonami dato che questo è javascript non java. Il punto dovrebbe essere lo stesso.

Visita link , fai clic sulla scheda javascript e incollalo:

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
  }
}

Dovresti notare che dir , wid e dis non vengono passati molto. Dovresti anche notare che il codice nella funzione che lock() restituisce assomiglia molto allo pseudo codice. Bene, è un codice reale. Eppure è molto facile da leggere. Potremmo aggiungere passaggi e assegnamenti, ma ciò aggiungerebbe confusione che non vedresti mai in pseudo codice.

Se vuoi sostenere che non fare l'assegnamento e il passaggio va bene perché quei tre vars sono stati persistenti allora considera una riprogettazione che assegna dir un valore casuale ad ogni ciclo. Non è persistente ora, vero?

Certo, ora potremmo abbassare dir in ambito ora, ma non senza essere costretti a ingombrare il nostro pseudo codice con il passaggio e l'impostazione.

Quindi no, lo stato non è il motivo per cui decidi di usare o meno gli effetti collaterali invece di passare e tornare. Né gli effetti collaterali da soli significano che il tuo codice è illeggibile. Non si ottengono i vantaggi del puro codice funzionale . Ma fatto bene, con buoni nomi, in realtà possono essere piacevoli da leggere.

Questo non vuol dire che non possano trasformarsi in spaghetti come un incubo. Ma allora, cosa non può?

Buona caccia alle anatre.

    
risposta data 22.04.2016 - 05:51
fonte

Leggi altre domande sui tag