2010-05-25 7 views
19

Diciamo che ho una classe JavaEsecuzione di un metodo dopo il costruttore di una classe derivata

abstract class Base { 
    abstract void init(); 
    ... 
} 

e so che ogni classe derivata dovrà chiamare init() dopo che è costruito. Potrei, naturalmente, è sufficiente chiamare nei costruttori delle classi derivate:

class Derived1 extends Base { 
    Derived1() { 
     ... 
     init(); 
    } 
} 

class Derived2 extends Base { 
    Derived2() { 
     ... 
     init(); 
    } 
} 

ma questo si rompe 'Non ripetere te stesso' principio piuttosto male (e ci stanno per essere molte sottoclassi di Base). Naturalmente, la chiamata init() non può andare al costruttore Base(), dal momento che sarebbe stato giustiziato troppo presto.

Delle idee come aggirare questo problema? Sarei abbastanza felice di vedere anche una soluzione di Scala.

UPDATE: Ecco una versione generica del metodo Factory Method:

interface Maker<T extends Base> { 
    T make(); 
} 

class Base { 
    ... 
    static <T extends Base> T makeAndInit(Maker<T> maker) { 
     T result = maker.make(); 
     result.init(); 
     return result; 
    } 
} 

UPDATE 2: Questa domanda è fondamentalmente "come si usa Template Method per i costruttori"? E la risposta sembra essere, "Puoi, ma è una cattiva idea". Quindi posso creare una fabbrica di modelli (metodo di template + factory astratta).

+2

Se init() è astratto nella classe base, allora perché ogni classe derivata deve chiamarla? Cosa succede se non lo fanno? –

+1

Perché non chiamate init() nel costruttore della classe 'Base'? – aioobe

+0

@Skip Head: non saranno inizializzati correttamente. –

risposta

10

Cosa succede in init()? È probabile che un design migliore possa eliminare del tutto il metodo, o almeno allentare il requisito che viene eseguito dopo il costruttore della sottoclasse. Assicurati che init() non renda visibile l'oggetto in costruzione a nessun altro thread prima del completamento del costruttore, poiché ciò crea bug di concorrenza.

Come alternativa (brutto), un metodo astratto potrebbero essere attuate da sottoclassi come uno pseudo-costruttore:

abstract class Base { 
    Base() { 
    ctor(); 
    init(); 
    } 
    abstract void ctor(); 
    abstract void init(); 
} 
+0

In realtà, questo mi sembra migliore di altre soluzioni (brutto, ma meno brutto delle alternative). –

10

evitare questo. Se lo si fa, ogni classe che estende la classe DerivedX può decidere di chiamare anche init() lasciando così l'oggetto in stato incoerente.

Un approccio è consentire al metodo init() di essere richiamato manualmente dai client della classe. Avere un campo initialized, e buttare IllegalStateExcepion se qualsiasi metodo che richiede l'inizializzazione viene chiamato senza di essa.

Un approccio migliore sarebbe quella di utilizzare un metodo factory statico invece di costruttori:

public Derived2 extends Base { 
    public static Derived2 create() { 
     Derived2 instance = new Dervied2(); 
     instance.init(); 
     return instance; 
    } 
} 

Aggiornamento: Come lei suggerisce nel vostro aggiornamento, è possibile passare Builder ad un metodo factory statica, che chiamerà il init() sull'istanza. Se le sottoclassi sono poche, penso che questa sia una complicazione, comunque.

+1

Questo mi lascia ancora chiamare 'init()' separatamente nel metodo factory per 'Derived1',' Derived2', ecc. –

+0

sì, la tua versione builder è una buona aggiunta. – Bozho

+0

Re l'ultima frase, dalla domanda: "(e ci saranno molte sottoclassi di Base)". –

6

Oltre a recommendation di Bozho, un contenitore di applicazioni è eccellente per l'attività.

Contrassegnare il proprio metodo init() con l'annotazione javax.annotation.PostConstruct e un contenitore EJB o Spring correttamente configurato eseguirà il metodo dopo che le iniezioni di dipendenza sono terminate, ma prima che l'oggetto possa essere utilizzato dall'applicazione.

Un metodo esempio:

@PostConstruct 
public void init() { 
    // logic.. 
} 

In un'applicazione enterprise è possibile aprire le risorse per ad esempio il sistema di file nel metodo init(). Questa inizializzazione può generare eccezioni e non dovrebbe essere chiamata da un costruttore.

+0

Per curiosità. Perché non si dovrebbe gettare dai costruttori ma dai metodi come 'init()'? – Tarrasch

1

Se siete avversi ad usare le fabbriche, per qualche motivo, è possibile utilizzare il seguente trucco:

trait RunInit { 
    def init():Unit 
    init() 
} 

class Derived1 extends Base with RunInit { 
    def init() = println("INIT'ing!") 
} 

Questo farà eseguire init() prima che il costruttore Derived1/corpo.

+1

Il problema è precisamente che voglio che 'init' esegua _after_ il costruttore/corpo' Derived1'. –

2

se Java ce l'avesse, non vedremmo tutte le chiamate di metodo init() in the wild.

"costruttore figlio surround con qualcosa" - che non può essere eseguito in puro java. Peccato, perché ci possono essere applicazioni molto interessanti, specialmente con il blocco di inizializzazione dell'istanza di classe anonima +.

fabbrica e contenitore: possono essere utili quando il codice nativo new non esegue il lavoro; ma è banale e noioso e non funzionerà con le classi anonime.

Problemi correlati