2015-06-12 14 views
6
public class App { 
    private final A a; 
    private final Server server; 
    public App(){ 
     a = new A(this); //Bad, this is escaping before it's initialized. 
    } 
    @Subscribe //This event fires some time after App is finished constructing. 
    public void registerStuff(RegisterEvent event){ 
     server = event.getServer(); //Not possible due to final field and this not being the constructor, is there such thing as a lazy final? 
     a.register(); 
    } 
} 

public class A { 
    private final App app; 
    private final BuilderSpec spec; 
    public A(App app){ 
     this.app = app; 
     this.spec = app.getServer().builder(this.app).build(); 
    } 
    public void register(){ 
     app.getServer().doStuff(this.app, this.spec); 
    } 
} 

Ho letto un po 'di ciò che "questo" fuga è e rendersi conto che il codice precedente è male, come ho idea di cosa i processi esterni stanno facendo con questo riferimento, quindi non dovrebbe essere passato al di fuori del costruttore fino a quando non viene costruito.Prevenire "questa" fuga in corso d'opera con campi finali

Tuttavia, a causa dei campi finali in App e A, non vedo davvero come posso inizializzarlo in seguito, o pigramente. Rendere i campi definitivi meno importanti di quelli che scappano? Non ne sono sicuro.

+1

Non è automaticamente male, e quello che hai va bene. Devi solo fare attenzione che il tuo oggetto sia in uno stato consistente prima di passare 'this' fuori dal costruttore. Deve essere evitato, sì, ma se si ha una dipendenza circolare (come si fa) e ciò che entrambi gli elementi devono essere "definitivi", non c'è alternativa. –

+0

Avere una tale dipendenza circolare non è molto buona immagino ... no ?? – Codebender

+1

Le dipendenze circolari di @AbishekManoharan sono perfettamente naturali. Si verificano tutto il tempo in modo completamente organico e fanno certamente parte della normale modellazione OO. –

risposta

2

Le dipendenze del costruttore possono diventare un problema se vengono attivate. La maggior parte dei contenitori IoC funziona con metodi di post-costruzione per evitare problemi con l'istanziazione degli oggetti.

Applicando il principio per il codice, sarebbe simile

class App { 
    private A a; 
    public App() { 
     // not necessary to have, just to show that we have no logic inside 
    } 
    protected void init() { // choose access modifiers 
     // init something in App; 
     a = new A(this); // safe to pass this now.   
    } 
} 

class A { 
    private final App app; 

    public A(App app) { 
     this.app = app; 
     // do whatever you need 
     // if logic affects other components, create init() method here 
    }  
} 

Sì, un riferimento in App non è definitiva. Ma questo design è più naturale e meno soggetto a errori.

+0

Come apparirebbe il codice nella P.S. Astuccio? È preferibile che A abbia un riferimento finale ad App e sia reintegrato se necessario, piuttosto che un riferimento all'app che potrebbe eventualmente cambiare in modo da sincronizzare lo stato di A. –

+0

@RyanTheLeach sì, sento che l'app finale è migliore, modifico il codice. – AdamSkywalker

+0

init() sta iniziando a sembrare molto simile a quello che verrebbe chiamato da EventHandlers annotato @Subscribe per registrare/inizializzare le cose. C'è qualche differenza nell'avere questa logica nel costruttore nel metodo init?Sembra proprio che qualsiasi problema che ho avuto in precedenza sia ora trasferito a se l'oggetto sta finendo di inizializzare invece che se l'oggetto ha finito di costruire? –

1

Come menzionato da @Boris_the_Spider, passare il numero this nel costruttore non è necessariamente una cosa negativa se si assicura che la classe sia in uno stato coerente (vedere Passing “this” in java constructor). Tuttavia, fai attenzione ad estendere App in futuro perché in questo caso le cose possono andare molto male (vedi Don't publish the "this" reference during construction)

Tuttavia, un'altra opzione valida (come hai menzionato) sarebbe quella di rimuovere finale da A. In questo caso, consiglierei di dichiarare un metodo getter. Perché A è privato, sarà accessibile solo per classe App, e quindi è solo bisogno di assicurarsi che App utilizza il metodo getA() ogni volta che è necessario accedere a A:

public class App { 
    private A a; 
    private final Server server; 
    public App(){} 

    // Define getter 
    private A getA(){ 
     return a == null ? new A(this) : a; 
    } 

    @Subscribe 
    public void registerStuff(RegisterEvent event){ 
     server = event.getServer(); 

     // Use getter 
     getA().register(); 
    } 
} 
+0

(-1 non mio) sembra che 'registerStuff' sia attivato da' new A() ':) il problema serio è che un misterioso oggetto' Server' è necessario durante l'inizializzazione di 'A' e' a', eppure può uscire solo dal nulla. – ZhongYu

+0

@ bayou.io è vero? Sembra l'unico metodo di 'A',' register() 'è chiamato all'interno di' registerStuff' – bcorso

+0

Spiacente, avrei dovuto fornire un contesto per RegisterEvent. RegisterEvent si verifica qualche tempo dopo che l'app è stata creata e non viene attivata mentre il costruttore non è terminato. –

1

Il codice non viene compilato dal momento che don' t inizializzare la variabile Server server che è final. In ogni caso è possibile passare uno Provider che ha il compito di fornire lo App al proprio A quando diventa disponibile (completamente inizializzato). Il codice può assomigliare a questo:

public App(){ 
    appProvider = new Provider<App>() 
    { 
     private volatile App app; 

     // setter here 

     @Override 
     public String get() 
     { 
      return app; 
     } 
    }; 
    a = new A(appProvider); 

} 

@Subscribe 
public void registerStuff(RegisterEvent event){ 
    // set server to your App 
    appProvider.set(this); 
} 

allora è possibile controllare in A se get rendimenti del provider null o meno. Credo che il pacchetto javax.inject contenga un'interfaccia appropriata per l'interfaccia Provider.

Il metodo registerStuff sembra buono per fornire il App poiché a quel punto è completamente inizializzato. Questo risponde anche alla tua domanda sull'inizializzazione pigra.

+0

Sì. Sono consapevole che il codice corrente nella domanda non viene compilato, ma mostra chiaramente il mio problema. –

+0

'A' non usa solo' Server', ha bisogno di 'App' per il builder:' this.spec = app.getServer(). Builder (this.app) .build(); ' – bcorso

+0

Vedo. Puoi scrivere un 'Provider' per la' App' stessa e fornire la tua 'App' quando hai anche il' Server' pronto. –

1

Hai bisogno anche di una classe App? A sembra aver bisogno solo di un Server. Basta spostare registerStuff() a da App a A e utilizzare solo A.

public class A{ 



@Subscribe 
public void registerStuff(RegisterEvent event){ 
    Server server = event.getServer(); 
    BuilderSpec spec = server.builder(this) // not sure what `builder()` needs but might only be A ? 
           .build(); 

    server.doStuff(this, spec); 
} 
} 
+0

Ho assolutamente bisogno di una classe "App", l'App è il punto di ingresso per l'intero plugin. Viene creato ed è quindi responsabile della ricezione di eventi o della registrazione di classi che invece riceverebbero eventi. –

+0

Questo è quello che pensavo, ma questa linea lo uccide: 'this.spec = app.getServer(). Builder (this.app) .build()' –

+0

L'app ha altri campi? – dkatzel