2010-03-24 13 views
9

Ci sono diversi modi in cui posso inizializzare oggetti complessi (con dipendenze iniettate e configurazione richiesta di membri iniettati), tutti sembrano ragionevoli, ma presentano vari vantaggi e svantaggi. Vi darò un esempio concreto:È una pratica buona o cattiva chiamare i metodi di istanza da un costruttore java?

final class MyClass { 
    private final Dependency dependency; 
    @Inject public MyClass(Dependency dependency) { 
    this.dependency = dependency; 
    dependency.addHandler(new Handler() { 
     @Override void handle(int foo) { MyClass.this.doSomething(foo); } 
    }); 
    doSomething(0); 
    } 
    private void doSomething(int foo) { dependency.doSomethingElse(foo+1); } 
} 

Come si può vedere, il costruttore fa 3 cose, tra cui chiamare un metodo di istanza. Mi è stato detto che chiamare i metodi di istanza da un costruttore non è sicuro perché aggira i controlli del compilatore per i membri non inizializzati. Cioè Avrei potuto chiamare doSomething(0) prima di impostare this.dependency, che avrebbe compilato ma non funzionato. Qual è il modo migliore per refactoring questo?

  1. Rendi statico lo doSomething e passare la dipendenza in modo esplicito? Nel mio caso reale ho tre metodi di istanza e tre campi membri che dipendono tutti l'uno dall'altro, quindi questo mi sembra un gran numero di hotplate in più per rendere tutti e tre questi statici.

  2. Spostare addHandler e doSomething in un metodo @Inject public void init(). Mentre l'uso con Guice sarà trasparente, richiede qualsiasi costruzione manuale per essere sicuro di chiamare init() oppure l'oggetto non sarà completamente funzionale se qualcuno dimenticherà. Inoltre, questo espone più dell'API, che sembrano entrambe idee sbagliate.

  3. Avvolgere una classe annidata per mantenere la dipendenza per assicurarsi che si comporta correttamente senza esporre ulteriore API:

    class DependencyManager { 
        private final Dependency dependency; 
        public DependecyManager(Dependency dependency) { ... } 
        public doSomething(int foo) { ... } 
    } 
    @Inject public MyClass(Dependency dependency) { 
        DependencyManager manager = new DependencyManager(dependency); 
        manager.doSomething(0); 
    }
    Questo tira metodi di istanza su tutti i costruttori, ma genera un ulteriore livello di classi, e quando ho già avuto interiore e classi anonime (ad esempio il gestore) può diventare confuso - quando ho provato questo mi è stato detto di spostare il DependencyManager in un file separato, che è anche di cattivo gusto perché ora è più file per fare una singola cosa.

Quindi qual è il modo preferito per affrontare questo tipo di situazione?

+1

@Steve: Ho appena rimosso i primi tag "pre" in modo che il codice mostri la sintassi con codice colore :) – SyntaxT3rr0r

+0

Cool, non sapevo che funzionasse in quel modo. – Steve

risposta

8

Josh Bloch in Java efficace raccomanda di utilizzare un metodo factory statico, anche se non riesco a trovare argomenti per casi come questo. Esiste, tuttavia, un caso simile in Java Concurrency in Practice, specificamente inteso a impedire la fuoriuscita di un riferimento a this dal costruttore. Applicato a questo caso, sembrerebbe:

final class MyClass { 
    private final Dependency dependency; 

    private MyClass(Dependency dependency) { 
    this.dependency = dependency; 
    } 

    public static createInstance(Dependency dependency) { 
    MyClass instance = new MyClass(dependency); 
    dependency.addHandler(new Handler() { 
     @Override void handle(int foo) { instance.doSomething(foo); } 
    }); 
    instance.doSomething(0); 
    return instance; 
    } 
    ... 
} 

Tuttavia, questo potrebbe non funzionare bene con l'annotazione DI che si utilizza.

+0

Sto usando Guice (beh, in effetti, Gin) - non supporta * direttamente * le fabbriche statiche, ma non impedisce nemmeno loro - Ho solo bisogno di aggiungere una copia extra del metodo factory nel 'GinModule'. L'altra alternativa è di creare una classe nidificata 'Provider 'e annotare' MyClass' come '@ProvidedBy (MyClass.MyProvider.class)'. Stavo cercando di evitare i metodi statici di fabbrica in quanto la statica può causare molti problemi con i test, ma ho capito che il costruttore è effettivamente la stessa cosa. – Steve

0

Sì, in realtà è illegale, in realtà non dovrebbe anche compilare (ma credo lo fa)

consideri il builder invece (e inclinarsi verso immutabile che in termini builder significa che non si può chiamare ogni setter due volte e non può chiamare nessun setter dopo che l'oggetto è stato "usato" - chiamare un setter a quel punto dovrebbe probabilmente lanciare un'eccezione di runtime).

Potete trovare le slide di Joshua Bloch sul (nuovo) builder in una presentazione di diapositive chiamato "Effective Java Reloaded: questa volta è per il Real", per esempio qui:

http://docs.huihoo.com/javaone/2007/java-se/

+1

Il fatto che compili non rende "illegale"; c'è una differenza tra cattiva pratica/"non fare questo" e illegale –

+0

Hai ragione, probabilmente ho usato la parola sbagliata, ma qualsiasi programma di lanciente decente lo mostrerebbe come minimo un avvertimento e forse un errore - I Non sono sicuro del motivo per cui lo permettano, ma poi non sono ancora sicuro del motivo per cui consentono ai membri pubblici di Java ... È tutto un mistero. –

0

È possibile utilizzare un metodo statico che accetta la dipendenza e costruisce e restituisce una nuova istanza e contrassegna il costruttore Friend. Non sono sicuro che Friend esista anche in java (è protetto da pacchetti). Tuttavia, questo potrebbe non essere il modo migliore. Puoi anche usare un'altra classe che è una Fabbrica per la creazione di MyClass.

Modifica: Wow un altro post ha appena suggerito la stessa identica cosa. Sembra che tu possa rendere privati ​​i costruttori in Java. Non è possibile farlo in VB.NET (non sono sicuro di C#) ... molto bello ...

9

Inoltre, con l'ereditarietà si danneggia molto. Se il tuo costruttore viene chiamato nella catena per istanziare una sottoclasse della tua classe, puoi chiamare un metodo che è sovrascritto nella sottoclasse e fa affidamento su un invariante che non è stato stabilito fino a quando il costruttore della sottoclasse non è stato eseguito.

+0

Buon punto: non ci avevo pensato! In questo caso, non è un problema poiché 'MyClass' è' final', ma è qualcosa da tenere a mente. – Steve

4

Si consiglia di prestare attenzione all'utilizzo dei metodi di istanza all'interno del costruttore, poiché la classe non è ancora stata completamente costruita. Se un metodo chiamato utilizza un membro che non è ancora stato inizializzato, beh, le cose brutte accadranno.

Problemi correlati