2010-05-25 11 views
19

Digamos que tengo una clase JavaEjecución de un método después del constructor de cualquier clase derivada

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

y sé que cada clase derivada tendrá que llamar init() después de que se construye. Podría, por supuesto, sólo tiene que llamar en los constructores de las clases de derivados:

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

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

pero esto rompe 'No te repitas' principio bastante mal (y no van a ser muchas subclases de Base). Por supuesto, la llamada init() no puede entrar en el constructor Base(), ya que se ejecutará demasiado pronto.

¿Alguna idea de cómo evitar este problema? Estaría muy feliz de ver una solución Scala, también.

ACTUALIZACIÓN: He aquí una versión genérica del enfoque Método de fábrica:

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; 
    } 
} 

ACTUALIZACIÓN 2: Esta pregunta es, básicamente, "¿cómo se utiliza el método de plantilla para los constructores"? Y la respuesta parece ser: "Puedes, pero es una mala idea". Así que puedo hacer una fábrica de plantillas (Método de plantilla + Fábrica abstracta) en su lugar.

+2

Si init() es abstracto en la clase base, ¿por qué cada clase derivada debe llamarlo? ¿Qué pasa si no lo hacen? –

+1

¿Por qué no llamas a init() en el constructor de la clase 'Base'? – aioobe

+0

@Skip Head: no se inicializarán correctamente. –

Respuesta

10

¿Qué sucede en init()? Es probable que un mejor diseño elimine por completo el método, o al menos relaje el requisito de que se ejecute después del constructor de la subclase. Asegúrese de que init() no haga que el objeto en construcción sea visible para ningún otro subproceso antes de que el constructor finalice, porque eso crea errores de concurrencia.

Como alternativa (feo), un método abstracto podrían ser implementadas por subclases como un pseudo-constructor:

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

En realidad, esto se ve mejor que otras soluciones para mí (feo, pero menos feo que las alternativas). –

10

Evita esto. Si lo hace, cualquier clase que amplíe su clase DerivedX puede decidir también llamar al init() dejando el objeto en estado incoherente.

Un enfoque es permitir que el método init() sea invocado manualmente por los clientes de su clase. Tener un campo initialized y lanzar IllegalStateExcepion si se llama a cualquier método que requiera inicialización sin él.

Un mejor enfoque sería el uso de un método de fábrica estática en lugar de constructores:

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

Actualización: Como se sugiere en su actualización, puede pasar Builder a un método de fábrica estática, que se llame a la init() en la instancia. Si sus subclases son pocas, creo que esto es una complicación excesiva, sin embargo.

+1

Esto todavía me deja llamando 'init()' por separado en el método de fábrica para 'Derived1',' Derived2', etc. –

+0

sí, la versión de su constructor es una buena adición. – Bozho

+0

Re su última frase, de la pregunta: "(y habrá muchas subclases de Base)". –

6

Además de Bozho recommendation, un contenedor de aplicaciones es excelente para la tarea.

Marque su método init() con la anotación javax.annotation.PostConstruct y un contenedor EJB o Spring configurado correctamente ejecutará el método después de que se completen las inyecciones de dependencia, pero antes de que la aplicación pueda usar el objeto.

Un ejemplo de método:

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

En una aplicación de empresa puede abrir recursos para, por ejemplo, el sistema de archivos en el método init(). Esta inicialización puede arrojar excepciones y no debe ser llamada desde un constructor.

+0

Por curiosidad. ¿Por qué no se debería tirar desde constructores, sino desde métodos 'init()'? – Tarrasch

1

Si usted es adversa a la utilización de las fábricas, por alguna razón, se puede utilizar el siguiente truco:

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

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

Esto ejecutará init() antes de que el constructor/Derived1 cuerpo.

+1

El problema es precisamente que quiero que 'init' ejecute _after_ el constructor/cuerpo' Derived1'. –

2

si Java lo tuviera, no veríamos todas estas llamadas al método init() en la naturaleza.

"rodear constructor hijo con algo" - eso no se puede hacer en Java puro. Lástima, porque puede haber aplicaciones muy interesantes, especialmente con el bloque de inicialización anónimo clase + instancia.

fábrica y contenedor: pueden ser útiles cuando el new nativo no hace el trabajo; pero eso es trivial y aburrido, y no funcionará con clases anónimas.

Cuestiones relacionadas