2015-12-15 20 views
5

ho un'interfaccia generica HandlerCome implementare pattern factory con generici in Java?

public interface Handler<T> { 
    void handle(T obj); 
} 

posso avere n implementazioni di questa interfaccia. Diciamo che ho seguito 2 implementazioni per ora. Uno che si occupa di oggetti String e un altro maniglie Data Visto

public class StringHandler implements Handler<String> { 
    @Override 
    public void handle(String str) { 
    System.out.println(str); 
    } 
} 

public class DateHandler implements Handler<Date> { 
    @Override 
    public void handle(Date date) { 
    System.out.println(date); 
    } 
} 

Voglio scrivere una fabbrica che restituirà le istanze del gestore in base al tipo di classe. Qualcosa di simile a questo:

class HandlerFactory { 
    public <T> Handler<T> getHandler(Class<T> clazz) { 
    if (clazz == String.class) return new StringHandler(); 
    if (clazz == Date.class) return new DateHandler(); 
    } 
} 

vengo seguente errore in questa fabbrica:

Tipo non corrispondente: non può convertire da StringHandler a Handler<T>

Come risolvere questo problema?

+1

Non so perché si vuole andare così in profondità nel generici. Perché non basta avere un metodo getStringHandler() e un metodo getDateHandler() ed essere fatto con esso? ..... è più chiaro all'utente in questo modo. –

+4

Non puoi fare ciò che stai tentando nel tuo esempio. Il compilatore non può seguire la tua logica che se 'clazz' è' String.class' allora 'T' è' String'. È ovvio per te, ma non per il compilatore. – Kayaman

+0

@OliverWatkins Vuoi dire che dovrei cambiare l'interfaccia di Handler per prendere Object invece di digitare T e lanciare Object sui rispettivi tipi nell'implementazione delle classi? Ma questo non violerebbe il Principio di sostituzione di Liskov? – user1522820

risposta

4

soluzione semplice

Si potrebbe salvare le mappature Class<T> -> Handler<T> in un Map.Qualcosa di simile:

Map<Class<T>, Handler<T>> registry = new HashMap<>(); 

public void registerHandler(Class<T> dataType, Class<? extends Handler> handlerType) { 
    registry.put(dataType, handlerType); 
} 

public <T> Handler<T> getHandler(Class<T> clazz) { 
    return registry.get(clazz).newInstance(); 
} 

In qualche luogo, inizializzare i gestori (potrebbe essere nella fabbrica stessa):

factory.registerHandler(String.class, StringHandler.class); 
factory.registerHandler(Date.class, DateHandler.class); 

E in un altro luogo, si crea e li usa:

Handler<String> stringhandler = factory.getHandler(String.class); 
Handler<Date> dateHandler = factory.getHandler(Date.class); 

PIÙ COMPLESSO SOLUZIONE

È possibile "scansionare" le classi usano la riflessione e, invece di registrare manualmente i mapping Class<T> -> Handler<T>, lo fanno usando la riflessione.

for (Class<? extends Handler> handlerType : getHandlerClasses()) { 
    Type[] implementedInterfaces = handlerType.getGenericInterfaces(); 
    ParameterizedType eventHandlerInterface = (ParameterizedType) implementedInterfaces[0]; 
    Type[] types = eventHandlerInterface.getActualTypeArguments(); 
    Class dataType = (Class) types[0]; // <--String or Date, in your case 
    factory.registerHandler(dataType, handlerType); 
} 

Poi, si crea e li usa come sopra:

Handler<String> stringhandler = factory.getHandler(String.class); 
Handler<Date> dateHandler = factory.getHandler(Date.class); 

Per attuare getHandlerClasses(), guarda this per la scansione di tutte le classi nel vostro jar. Per ogni classe, è necessario verificare se si tratta di un Handler:

if (Handler.class.isAssignableFrom(scanningClazz) //implements Handler 
    && scanningClazz.getName() != Handler.class.getName()) //it is not Handler.class itself 
{ 
     //is a handler! 
} 

Speranza che aiuta!

+0

Grazie..Grande risposta – user1522820

+0

In aggiunta a questa risposta, se si utilizza Java 8, mi libererei completamente di una classe factory separata e inserisco il metodo statico di fabbrica nell'interfaccia Handler stessa: questo è logico e conveniente. – yvolk

-1

Fondamentalmente si può fare:

public Handler getHandler(Class clazz){ 
     if(clazz == String.class) return new StringHandler(); 
     if(clazz == Date.class) return new DateHandler(); 

     return null; 
    } 

    public static void main(String[] args){ 
     HandlerFactory handlerFactory = new HandlerFactory(); 
     StringHandler handler = (StringHandler)handlerFactory.getHandler(String.class); 
     handler.handle("TEST"); 
     DateHandler handler2 = (DateHandler)handlerFactory.getHandler(Date.class); 
     handler2.handle(new Date()); 
    } 

uscita:

TEST 
Tue Dec 15 15:31:00 CET 2015 

Ma invece scrivere due metodi diversi per ottenere i gestori separatamente sempre è un modo migliore.

0

si può usare qualcosa di simile:

class HandlerFactory { 
    public <T> Handler<T> getHandler(Class<T> clazz) { 
    if (clazz.equals(String.class)) return (Handler<T>) new StringHandler(); 
    if (clazz.equals(Date.class)) return (Handler<T>) new DateHandler(); 

    return null; 
    } 
} 

T è generico e il compilatore non può mappare che al momento della compilazione. Inoltre è più sicuro usare .equals anziché ==.

-1

Ho modificato il codice e ho permesso a Eclipse di "correggere" gli errori e questo si è risolto.

public Handler<?> getHandler(Class<?> clazz) { 
    if (clazz == String.class) 
     return new StringHandler(); 
    if (clazz == Date.class) 
     return new DateHandler(); 

    return null; 
} 
-1

Yout HandlerFactory non conoscono T. Usa la tua fabbrica come sotto-

public class HandlerFactory { 
    public Handler<?> getHandler(Class<?> clazz) { 
     if (clazz == String.class) { 
      return new StringHandler(); 
     } 
     if (clazz == Date.class) { 
      return new DateHandler(); 
     } 
     return null; 
    } 
} 
2

Il tuo problema è che il compilatore non può fare il salto al fatto thet il tipo del risultato è corretto.

Per aiutare il compilatore è possibile fare in modo che la fabbrica deleghi la costruzione. Sebbene questo sembri strano e poco maneggevole, riesce a mantenere correttamente la sicurezza del tipo senza sacrifici come la trasmissione o l'uso di ? o tipi non elaborati.

public interface Handler<T> { 

    void handle(T obj); 
} 

public static class StringHandler implements Handler<String> { 

    @Override 
    public void handle(String str) { 
     System.out.println(str); 
    } 
} 

public static class DateHandler implements Handler<Date> { 

    @Override 
    public void handle(Date date) { 
     System.out.println(date); 
    } 
} 

static class HandlerFactory { 

    enum ValidHandler { 

     String { 
        @Override 
        Handler<String> make() { 
         return new StringHandler(); 
        } 
       }, 
     Date { 
        @Override 
        Handler<Date> make() { 
         return new DateHandler(); 
        } 
       }; 

     abstract <T> Handler<T> make(); 
    } 

    public <T> Handler<T> getHandler(Class<T> clazz) { 
     if (clazz == String.class) { 
      return ValidHandler.String.make(); 
     } 
     if (clazz == Date.class) { 
      return ValidHandler.Date.make(); 
     } 
     return null; 
    } 
} 

public void test() { 
    HandlerFactory factory = new HandlerFactory(); 
    Handler<String> stringHandler = factory.getHandler(String.class); 
    Handler<Date> dateHandler = factory.getHandler(Date.class); 
} 
0

L'intero punto di utilizzo di un tipo generico consiste nel condividere l'implementazione. Se l'implementazione n dell'interfaccia di Handler è così diversa da non poter essere condivisa, allora non penso che ci sia una ragione per usare definire quell'interfaccia generica in primo luogo. Preferisci semplicemente StringHandler e DateHandler come classi di primo livello.

D'altra parte, se l'attuazione può essere condiviso, come è il caso del vostro esempio, allora la fabbrica funziona naturalmente:

public class Main { 
    static public interface Handler<T> { 
     void handle(T obj); 
    } 

    static public class PrintHandler<T> implements Handler<T> { 
     @Override 
     public void handle(T obj) { 
     System.out.println(obj); 
     } 
    } 

    static class HandlerFactory { 
     public static <T> Handler<T> getHandler() { 
     return new PrintHandler<T>(); 
     } 
    } 

    public static void main(String[] args) { 
     Handler<String> stringHandler = HandlerFactory.getHandler(); 
     Handler<Date> dateHandler = HandlerFactory.getHandler(); 

     stringHandler.handle("TEST"); 
     dateHandler.handle(new Date()); 
    } 
}