2009-02-24 10 views
64

Mi sono chiesto quale sia il modo migliore (ovvero più pulito/più sicuro/più efficiente) di gestire più costruttori in Java? Soprattutto quando in uno o più costruttori vengono specificati non tutti i campi:Il modo migliore per gestire più costruttori in Java

public class Book 
{ 

    private String title; 
    private String isbn; 

    public Book() 
    { 
     //nothing specified! 
    } 

    public Book(String title) 
    { 
     //only title! 
    } 

    ...  

} 

Cosa devo fare quando i campi non sono specificate? Finora ho usato i valori predefiniti nella classe in modo che un campo non sia mai nullo, ma che sia un "buon" modo di fare le cose?

+0

Dipende, avete bisogno di tutti i campi per contenere un valore? – CodeMonkey

+0

questo articolo potrebbe essere utile: http://www.yegor256.com/2015/05/28/one-primary-constructor.html – yegor256

+3

Non mi piace quando le persone fanno domande e quindi non accettano le risposte. – muni764

risposta

127

Una risposta leggermente semplificata:

public class Book 
{ 
    private final String title; 

    public Book(String title) 
    { 
     this.title = title; 
    } 

    public Book() 
    { 
     this("Default Title"); 
    } 

    ... 
} 
+6

+1 Trovo una buona regola empirica che tutti i costruttori debbano attraversare un "punto di strozzatura" comune. – cletus

+1

Inoltre, è sempre consigliabile avviare ogni costruttore con this() o super() = 8-) – Yuval

+0

Il concatenamento del costruttore è sempre buono. – Powerlord

32

consigliabile utilizzare il builder. Permette di impostare valori predefiniti sui parametri e inizializzarli in modo chiaro e conciso. Per esempio:


    Book b = new Book.Builder("Catcher in the Rye").Isbn("12345") 
     .Weight("5 pounds").build(); 

Edit: Inoltre rimuove la necessità di molteplici costruttori con firme diverse ed è il modo più leggibile.

+0

Questa è la migliore idea se hai un costruttore multiplo, puoi facilmente espanderlo per nuovi tipi e meno incline agli errori – dinsim

+0

@Dinesh - Sono d'accordo, io uso i Costruttori su tutto il mio codice. Adoro lo schema! – kgrad

+0

Nei miei opioni è come una Fluent Interface (http://www.codemonkeyism.com/archives/2007/10/10/fluent-interface-and-reflection-for-object-building-in-java/) – alepuzio

18

È necessario specificare quali sono gli invarianti di classe, ovvero le proprietà che saranno sempre vere per un'istanza della classe (ad esempio, il titolo di un libro non sarà mai nullo, o la dimensione di un cane sarà sempre > 0).

Questi invarianti dovrebbero essere stabiliti durante la costruzione e conservati durante la vita dell'oggetto, il che significa che i metodi non devono rompere gli invarianti. I costruttori possono impostare questi invarianti sia avendo argomenti obbligatori, oppure impostando valori di default:

class Book { 
    private String title; // not nullable 
    private String isbn; // nullable 

    // Here we provide a default value, but we could also skip the 
    // parameterless constructor entirely, to force users of the class to 
    // provide a title 
    public Book() 
    { 
     this("Untitled"); 
    } 

    public Book(String title) throws IllegalArgumentException 
    { 
     if (title == null) 
      throw new IllegalArgumentException("Book title can't be null"); 
     this.title = title; 
     // leave isbn without value 
    } 
    // Constructor with title and isbn 
} 

Tuttavia, la scelta di questi invarianti altamente dipende dalla classe che si sta scrivendo, come lo userete, ecc ., quindi non c'è una risposta definitiva alla tua domanda.

+0

Si desidera controllare che il null non venga passato a quel secondo costruttore. –

+0

Infatti, lo aggiungerò. –

3

Un'altra considerazione, se un campo è necessaria o ha una gamma limitata, eseguire il controllo nel costruttore:

public Book(String title) 
{ 
    if (title==null) 
     throw new IllegalArgumentException("title can't be null"); 
    this.title = title; 
} 
+1

Si dovrebbe fare questo per tutti i tuoi metodi pubblici. Controlla sempre gli argomenti, ti farà risparmiare qualche mal di testa lungo la strada. – cdmckay

6

Alcuni suggerimenti generali costruttore:

  • Cercare di concentrare tutta l'inizializzazione in un singolo costruttore e chiamarlo dagli altri costruttori
    • Questo funziona bene se esistono più costruttori per simulare i parametri predefiniti
  • Mai chiamare un metodo non-finale da un costruttore
    • metodi privati ​​sono definitive, per definizione,
    • polimorfismo può uccidere qui; si può finire per chiamare un'implementazione sottoclasse prima della sottoclasse è stato inizializzato
    • Se avete bisogno di "helper" metodi, assicurarsi di renderli privati ​​o finale
  • essere esplicito nelle vostre chiamate a super()
    • Sareste sorpresi di quanti programmatori Java non si rendono conto che super() viene chiamato anche se non lo si scrive esplicitamente (presupponendo che non si abbia una chiamata a questo (...))
  • Conoscere l'ordine delle regole di inizializzazione per i costruttori. E 'fondamentalmente:

    1. questo (...) se presente (solo mossa per un altro costruttore)
    2. chiamata super (...) [se non esplicito, chiamare super() implicitamente]
    3. (costruire superclasse utilizzando queste regole ricorsivamente)
    4. inizializzare i campi attraverso i loro dichiarazioni
    5. corpo corsa del costruttore corrente
    6. ritorno a costruttori precedenti (se si era incontrato questo (...) le chiamate)

Il flusso complessivo finisce per essere:

  • mossa tutta la strada fino alla gerarchia superclasse di opporsi
  • mentre non fatto
    • campi init
    • costruttore
    • run corpi
    • discesa a bclass

Per un bell'esempio del male, provare capire cosa stamperà quanto segue, quindi eseguirlo

package com.javadude.sample; 

/** THIS IS REALLY EVIL CODE! BEWARE!!! */ 
class A { 
    private int x = 10; 
    public A() { 
     init(); 
    } 
    protected void init() { 
     x = 20; 
    } 
    public int getX() { 
     return x; 
    } 
} 

class B extends A { 
    private int y = 42; 
    protected void init() { 
     y = getX(); 
    } 
    public int getY() { 
     return y; 
    } 
} 

public class Test { 
    public static void main(String[] args) { 
     B b = new B(); 
     System.out.println("x=" + b.getX()); 
     System.out.println("y=" + b.getY()); 
    } 
} 

io aggiungere commenti che descrivono il motivo per cui le opere di cui sopra come lo fa. .. Alcuni possono essere ovvi; alcuni non è ...

+0

1. Il metodo init() non viene mai chiamato quando si crea un B (B lo sovrascrive) –

+0

2. Il metodo init() di B viene chiamato dal costruttore di A –

+0

B's init() viene chiamato * prima che gli inizializzatori di * B vengano chiamati !. y viene assegnato a 42 * dopo * è assegnato in init() –

0

farei quanto segue:

 
public class Book 
{ 
    private final String title; 
    private final String isbn; 

    public Book(final String t, final String i) 
    { 
     if(t == null) 
     { 
      throw new IllegalArgumentException("t cannot be null"); 
     } 

     if(i == null) 
     { 
      throw new IllegalArgumentException("i cannot be null"); 
     } 

     title = t; 
     isbn = i; 
    } 
} 

sto facendo l'ipotesi che qui:

1) il titolo non cambierà mai (da cui il titolo è definitivo) 2) l'isbn non cambierà mai (quindi isbn è finale) 3) che non è valido avere un libro senza un titolo e un isbn.

consideri una classe Studente:

 
public class Student 
{ 
    private final StudentID id; 
    private String firstName; 
    private String lastName; 

    public Student(final StudentID i, 
        final String first, 
        final String last) 
    { 
     if(i == null) 
     { 
      throw new IllegalArgumentException("i cannot be null"); 
     } 

     if(first == null) 
     { 
      throw new IllegalArgumentException("first cannot be null"); 
     } 

     if(last == null) 
     { 
      throw new IllegalArgumentException("last cannot be null"); 
     } 

     id  = i; 
     firstName = first; 
     lastName = last; 
    } 
} 

C'è uno studente deve essere creato con un ID, un nome e un cognome. L'ID studente non può mai cambiare, ma lo stato di una persona e cognome può cambiare (sposarsi, cambiare nome a causa della perdita di una scommessa, ecc ...).

Al momento di decidere quali costruttori avere davvero bisogno di pensare a ciò che ha senso avere. Tutti a volte le persone aggiungono metodi set/get perché vengono insegnati - ma molto spesso è una cattiva idea.

Le classi immutabili sono molto meglio avere (cioè classi con variabili finali) su quelle mutabili. Questo libro: http://books.google.com/books?id=ZZOiqZQIbRMC&pg=PA97&sig=JgnunNhNb8MYDcx60Kq4IyHUC58#PPP1,M1 (efficace Java) ha una buona discussione sull'immutabilità. Guarda gli articoli 12 e 13.

+0

Perché definisci i parametri? I parametri finali –

+0

fanno in modo che tu non accidentalmente faccia t = titolo per esempio. – TofuBeer

9

Si dovrebbe sempre costruire un oggetto valido e legittimo; e se non è possibile utilizzare i paraps del costruttore, è necessario utilizzare un oggetto builder per crearne uno, rilasciando l'oggetto dal builder solo quando l'oggetto è completo.

Sulla questione dell'uso del costruttore: cerco sempre di avere un costruttore di base su cui tutti gli altri si rimettono, concatenando con i parametri "omessi" al successivo costruttore logico e terminando con il costruttore di base. Quindi:

class SomeClass 
{ 
SomeClass() { 
    this("DefaultA"); 
    } 

SomeClass(String a) { 
    this(a,"DefaultB"); 
    } 

SomeClass(String a, String b) { 
    myA=a; 
    myB=b; 
    } 
... 
} 

Se ciò non è possibile, provo ad avere un metodo init() privato a cui tutti i costruttori fanno riferimento.

E mantenere il numero di costruttori e parametri piccoli - un massimo di 5 di ciascuno come linea guida.

+0

yeap, più di cinque parametri nel costruttore e dovresti valutare l'implementazione di un costruttore objetc ... vedi Java efficace per maggiori dettagli ... – opensas

1

Potrebbe valere la pena considerare l'utilizzo di un metodo factory statico anziché di un costruttore.

sto dicendo invece, ma ovviamente non è possibile sostituire il costruttore. Quello che puoi fare, però, è nascondere il costruttore dietro un metodo statico di fabbrica. In questo modo, pubblichiamo il metodo factory statico come parte dell'API della classe, ma allo stesso tempo nascondiamo il costruttore rendendolo privato o privato del pacchetto.

Si tratta di una soluzione abbastanza semplice, soprattutto in confronto con il modello Builder (come si vede in Joshua Bloch di Effective Java 2nd Edition - attenzione, Banda dei Design Patterns di Quattro definiscono un modello di progettazione completamente diverso con lo stesso nome, in modo che possa essere leggermente confuso) che implica la creazione di una classe nidificata, un oggetto costruttore, ecc.

Questo approccio aggiunge un ulteriore livello di astrazione tra te e il tuo cliente, rafforzando l'incapsulamento e semplificando le modifiche lungo la strada. Fornisce anche il controllo dell'istanza: poiché gli oggetti vengono istanziati all'interno della classe, tu e non il cliente decidete quando e come questi oggetti vengono creati.

Infine, rende più semplice il test - fornendo un costruttore stupido, che assegna semplicemente i valori ai campi, senza eseguire alcuna logica o convalida, consente di introdurre uno stato non valido nel sistema per verificare come si comporta e reagisce a quella. Non sarai in grado di farlo se stai convalidando i dati nel costruttore.

Si può leggere molto di più che in (già citato) di Joshua Bloch Effective Java 2nd Edition - è uno strumento importante nella toolbox tutti gli sviluppatori e non c'è da meravigliarsi che sia l'oggetto del primo capitolo del libro. ;-)

Seguendo il tuo esempio:

public class Book { 

    private static final String DEFAULT_TITLE = "The Importance of Being Ernest"; 

    private final String title; 
    private final String isbn; 

    private Book(String title, String isbn) { 
     this.title = title; 
     this.isbn = isbn; 
    } 

    public static Book createBook(String title, String isbn) { 
     return new Book(title, isbn); 
    } 

    public static Book createBookWithDefaultTitle(String isbn) { 
     return new Book(DEFAULT_TITLE, isbn); 
    } 

    ... 

}

qualsiasi modo si sceglie, è una buona pratica per avere uno principale costruttore, che assegna solo ciecamente tutti i valori, anche se è usato solo da altri costruttori.