Java: come creare una classe wrapper per sottoclassi di classe astratta che istanziano la classe wrapped


Ho un'interfaccia (chiamiamola FooInterface) che implemento in varie sottoclassi (possiamo chiamarle X, Y, Z) di sottoclassi (Chiamiamole A, B, C) di una classe astratta (chiamiamola Bar). Ma sembra dispendioso dover scrivere (copiare) gli stessi metodi in tutti loro, ancor più considerando che non sono altro che la sottoclasse che estendono + quei metodi dall'interfaccia.

Come tale, ho deciso di rimuovere il livello X, Y, Z e rendere FooInterface una vera classe Foo avvolgimento Bar-istanze. Ma non riesco a capire un buon modo per farlo, il mio primo pensiero è stata una classe wrapper generica:

public abstract class Bar {/*code*/}

public class A extends Bar {/*code*/}
public class B extends Bar {/*code*/}
public class C extends Bar {/*code*/}

/*
 * Removed thanks to using the nifty wrapper Foo now!
 * I am so happy I decided to do this,
 *  can't imagine the horrors I would dream of at night if I had
 *  an extra redundant class for each subclass of Bar like this!
 * public class X extends A implements FooInterface {/*implementing FooInterface*/}
 * public class Y extends B implements FooInterface {/*implementing FooInterface*/}
 * public class Z extends C implements FooInterface {/*implementing FooInterface*/}
*/

public class Foo<T extends Bar> {
    private T t;
    public Foo() {t = /*code instantiating T*/}
    //code implementing the methods from FooInterface
}

Questo tuttavia non funzionerà, poiché sembra abbastanza difficile ottenere un'istanza di T quando si utilizzano tipi generici in java.

Ho considerato di fare qualcosa di simile ad avere T come enum invece:

public class Foo<T extends BarType> {
    public enum BarType { A, B, C; }
    private T t;
    public Foo() {
        switch(T) {
            // code instantiating t by using an ugly hard-coded switch-case...
            // please don't make me!
        }

        //code implementing the methods from FooInterface
    }
}

Ma non riesco onestamente a immaginare come si possa usare un enum come questo. Quindi a questo punto sto seriamente pensando di rinunciare a vuoto costruttori, e facendo qualcosa sulla falsariga di:

// No longer generic and instead pass type to constructor!
public class Foo {
    public enum BarType { A, B, C; }
    private T t;
    public Foo(Bartype bartype) {
        switch(T) {
            // code instantiating t by using an ugly hard-coded switch-case...
            // please don't make me!
        }

        //code implementing the methods from FooInterface
    }
}

Anche se per evitare di dover fare istruzioni switch hard-coded sono più incline a questa versione:

public class Foo<T extends Bar> {
    private T t;
    // Foo is used as such: 'Foo<T> foo = new Foo<T>(T.class);'
    public Foo(Class<T> clazz) throws InstantiationException,
            IllegalAccessException {
        // why is it customary to spell this with z?
        t = clazz.newInstance()

        //code implementing the methods from FooInterface
    }
}

Come faccio a creare un wrapper come questo correttamente in java? Ho considerato anche altri modi, come in una classe generica che passa una factory al costruttore o fa quel ParameterizedType thingie, o in modo non generico semplicemente passando un'istanza di classe corretta al costruttore direttamente. Ma tutti quelli sembra un grande seccatura e dannatamente brutto, che preferirei persino switch-case hard-coded con enumerazioni del genere...

MODIFICA: Come alcune persone hanno chiesto perché non faccio solo Bar implementare FooInterface, è perché Bar è una bella struttura dati pura, e il ruolo di FooInterface è quello di farlo avere alcuni metodi utili per un'implementazione più specializzata. Esempio, dire Bar ecc. sono elenchi normali (quindi è possibile che non sia nemmeno colui che li ha implementati), ma anche la mia implementazione deve tieni traccia di quali sono cool:

public abstract class List; // Bar
public class LinkedList extends List; // A
public class ArrayList extends List // B
public interface CoolLists { // FooInterface
    boolean isCool();
    void setCool(boolean cool);
}
public class CoolLinkedList extends LinkedList implements CoolList; // X
public class CoolArrayList extends ArrayList implements CoolList; // Y

E no, non sono in realtà che implementa le liste, e se volessi tenere traccia delle "liste cool" non memorizzerei un booleano in esse e userei invece qualcosa come un HashSet che memorizza quelle "cool": P

Author: felix, 2017-01-31

1 answers

Invece di passare un Class<T>, dovresti passare un Supplier<T> al costruttore di Foo. Per ottenere un'istanza, basta chiamare t = supplier.get();. Questo ti impedisce di avere a che fare con problemi di riflessione. Per ottenere un fornitore è possibile utilizzare sia A::new, B::new oppure C::new. Per creare un'istanza di Foo è necessario chiamare:

new Foo<A>(A::new);

Non vi è alcuna riflessione e nessuna gestione delle eccezioni coinvolti.

Vedi anche questa risposta.

 3
Author: Stefan Dollase, 2017-05-23 11:53:07