2015-06-12 19 views
17

Sto facendo un corso base di Java e ho avuto un problema: come posso creare un oggetto solo se ho passato parametri validi al costruttore?Java - Come creare solo un oggetto con attributi validi?

Devo fare una classe alternativa e chiamare il costruttore da lì dopo che la convalida è stata realizzata?

Oppure/dovrei usare un metodo statico nella classe per la convalida?

Qual è la migliore pratica in questo caso?

risposta

27

La pratica standard è di convalidare gli argomenti nel costruttore. Per esempio:

class Range { 
    private final int low, high; 
    Range(int low, int high) { 
    if (low > high) throw new IllegalArgumentException("low can't be greater than high"); 
    this.low = low; 
    this.high = high; 
    } 
} 

Nota a margine: per verificare che gli argomenti non sono nulli, che è abbastanza comune, è possibile utilizzare:

import static java.util.Objects.requireNonNull; 

Constructor(Object o) { 
    this.o = requireNonNull(o); //throws a NullPointerException if 'o' is null 
} 

UPDATE

Per rispondere alla tua specifica commento sul numero di previdenza sociale. Un modo potrebbe essere quello di aggiungere un metodo alla classe:

//constructor 
public YourClass(String ssn) { 
    if (!isValidSSN(ssn)) throw new IllegalArgumentException("not a valid SSN: " + ssn); 
    this.ssn = ssn; 
} 

public static boolean isValidSSN(String ssn) { 
    //do some validation logic 
} 

Il codice chiamante potrebbe allora apparire come:

String ssn = getSsnFromUser(); 
while(!YourClass.isValidSSN(ssn)) { 
    showErrorMessage("Not a valid ssn: " + ssn); 
    ssn = getSsnFromUser(); 
} 
//at this point, the SSN is valid: 
YourClass yc = new YourClass(ssn); 

Con questo disegno, avete raggiunto due cose:

  • tu convalidi l'input dell'utente prima di usarlo (cosa che dovresti sempre fare - gli utenti sono molto bravi negli errori di battitura)
  • ti sei assicurato che se YourClass è m isused viene generata un'eccezione e vi aiuterà a individuare i bug

Si potrebbe andare oltre creando una classe SSN che contiene lo SSN e incapsula la logica di convalida. YourClass accetta quindi un oggetto SSN come argomento che è sempre un SSN valido per costruzione.

+0

L'argomento illegale, come lei e altri hanno detto, avrebbe rotto il programma, giusto? –

+0

@TiagoSirious Cosa intendi per "pausa"? Uscirà immediatamente dal costruttore e genererà un'eccezione al chiamante. E lo vuoi perché hai ricevuto argomenti che non sai cosa fare con ... Quindi questo ti aiuterà a identificare un bug nel codice che chiama questo costruttore. – assylias

+0

Ok, ma immagina che l'utente superi un numero di previdenza sociale che non è valido. Lo voglio controllato, ma non voglio che l'applicazione smetta di funzionare. Voglio avvertire l'utente che questo SSN non è valido e voglio che reinserisca un SSN valido. In questo caso, se lancio l'eccezione, è solo il costruttore che esce o l'intera applicazione? Aggiornamento –

4

I costruttori possono generare eccezioni (vedere Can constructors throw exceptions in Java?) in modo che il costruttore possa generare un'eccezione se vengono passati valori non validi. Puoi anche rendere privato il tuo costruttore e utilizzare il metodo statico per creare il tuo oggetto, che esegue i controlli. Questo potrebbe essere più pulito.

9

avevo appena gettare un IllegalArgumentException nel costruttore stesso:

public class MyClass { 
    private int i; 

    public MyClass (int i) { 
     // example validation: 
     if (i < 0) { 
      throw new IllegalArgumentException ("i mustn't be negatve!"); 
     } 
     this.i = i; 
} 
-2

È una cattiva pratica lanciare un'eccezione da un costruttore. Si finisce con un oggetto parzialmente inizializzato, che probabilmente interromperà tutti i tipi di contratti.

Se un costruttore non è valido per tutte le combinazioni di input, è più pulito creare un metodo factory che convalida e rendere privato il costruttore. Se esiste una possibilità reale di errore (ovvero, l'errore non è dovuto a un errore di programmazione), potrebbe essere opportuno restituire uno Optional.

+0

Se non vi è alcuna perdita di costruttore (quando si assegna una variabile statica o si passa ** questa ** istanza a un altro thread - che è comunque una cattiva pratica), allora l'oggetto sarà spazzato via senza conseguenze. –

+1

Se un costruttore lancia un'eccezione, il nuovo oggetto non viene restituito al chiamante e non può causare alcun danno (a meno che, ovviamente, il costruttore non rilevi un riferimento all'oggetto in costruzione su un altro codice prima che sia completamente inizializzato, che è un cattivo pratica comunque). – meriton

7

Un truismo ben noto nella programmazione è 'Non utilizzare le eccezioni per il controllo del flusso'. Il tuo codice dovrebbe essere consapevole delle restrizioni e proteggerle prima di chiamare il costruttore piuttosto che gestire gli errori. Esistono delle eccezioni per circostanze di aspettativa, specialmente per quelle che non possono essere previste o protette (ad esempio, un flusso di IO può diventare non valido durante la scrittura, nonostante sia OK durante un controllo precedente).

Mentre è possibile generare eccezioni nel costruttore, questo non è sempre l'ideale. Se si stanno scrivendo oggetti pubblici che si prevede siano usati/riutilizzati da altri, le eccezioni sono l'unica opzione reale per i costruttori pubblici, tuttavia tali limitazioni e il loro risultato (ad esempio quale eccezione verrà lanciata) dovrebbero essere chiaramente documentati nella javadoc per classe.

Per le classi interne, le asserzioni sono più appropriate. Come afferma Oracle: "Le asserzioni ... dovrebbero essere utilizzate per verificare casi che non dovrebbero mai accadere, controllare ipotesi sulla struttura dei dati o imporre vincoli su argomenti di metodi privati." - Using Assertions in Java Technology. Probabilmente dovresti comunque documentare le tue aspettative per il corso, ma la tua applicazione dovrebbe effettuare internamente eventuali controlli piuttosto che affidarti a eventuali eccezioni generate.

I metodi di produzione statici possono aiutare un po ', i loro vantaggi si stanno elaborando un po' con un'altra domanda: How to use “Static factory methods” instead of constructors. Tuttavia, non offrono forti opzioni di convalida senza, ancora una volta, fare affidamento su Eccezioni quando le cose non sono valide (che, o restituendo null, che è meno informativo).

La soluzione ideale è la Builder pattern. Non solo consente una maggiore flessibilità nella gestione degli argomenti, è possibile convalidarli singolarmente o disporre di un metodo di convalida in grado di valutare tutti i campi contemporaneamente. Un costruttore può e deve essere utilizzato per nascondere il costruttore effettivo dell'oggetto, godendo del solo accesso ad esso e impedendo che vengano mai inviati valori indesiderati, mentre le asserzioni possono evitare che "il costruttore non debba mai inviare questi valori".

+0

Trovo che la frase "non utilizzare le eccezioni per il controllo del flusso" non abbia senso: poiché l'istruzione throw è un'istruzione di controllo (trasferisce il controllo ad un altro blocco di codice), ciò significa che il codice utente non deve mai generare un'eccezione. – meriton

+0

@meriton Credo che ci sia un "normale" implicito in là, vale a dire, "non usare le eccezioni per il controllo del flusso ** normale **". L'implicazione è che, se un'istruzione if può fare lo stesso lavoro, usalo invece, come qualificato nella mia risposta. – Danikov

+0

Ogni eccezione può essere sostituita da un'istruzione if (i programmatori C ottengono senza eccezioni, dopo tutto). – meriton

0

Se non si desidera generare un'eccezione dal costruttore, è possibile rendere privato il costruttore e creare un metodo statico che restituisce una nuova istanza dell'oggetto o null se gli argomenti non sono validi. Il chiamante di questo metodo dovrebbe verificare se il risultato è nullo o no, comunque.

Esempio:

public class Foo { 
    private Foo(int arg1, Bar arg2) { 
    // guaranteed to be valid. 
    } 

    public static Foo construct(int arg1, Bar arg2) { 
    // perform validation 
    if (arg1 < 0 || arg2 == null) { 
     return null; 
    } else { 
     return new Foo(arg1, arg2); 
    } 
    } 
} 

Uso

Foo object = Foo.construct(1, new Bar()); 
if (object == null) { 
    // handle error here. 
} 
+0

Qualcuno può controllare se questa è una buona idea? Sembra molto semplice e interessante per me. –

1

Un modo per assicurarsi di avere parametri validi passati al costruttore è quello di creare la classe genitore con costruttori che accettano solo la i parametri richiesti, quindi creare una sottoclasse utilizzata dagli utenti finali. Se costringi il tuo utente a chiamare super() e passare i parametri richiesti, devono almeno passare negli oggetti dati giusti. Per quanto riguarda i valori validi per questi parametri, dipende da te se vuoi includere la convalida nel costruttore della classe genitore e generare eccezioni di runtime o whatnot.

Ecco un esempio della superclasse/sottoclasse. Chiamiamo il superlcass SomeShape e la sottoclasse Triangle. Per qualsiasi oggetto SomeShape, si costringerà "l'utente" a fornire un numero di lati e una lunghezza laterale. Questo è come...

public class SomeShape { 
    private int numSides; 
    private int sideLength; 

    public SomeShape(int mNumSides, int mSideLength) { 
     numSides = mNumSides; 
     sideLength = mSideLength; 
    } 
} 

public class Triangle extends SomeShape { 
    private int height; 

    public Triangle(int mNumSides, int mSideLength, int mHeight) { 
     super(mNumSides, mSideLength); 
     height = mHeight; 
    } 
} 

Oltre a codificare un gruppo di logica ed eccezione gettando nel vostro costruttore, questo è un modo relativamente pulito per far rispettare quali parametri sono necessari per creare l'oggetto.

Problemi correlati