Quiero implementar un patrón de fábrica de singleton genérico donde paso la clase del objeto requerido como parámetro y la clase de fábrica debería verificar en el mapa si ya hay un objeto creado para él, si es así, devolver el objeto del mapa. Si no, cree una nueva instancia, colóquela en el mapa y devuelva la instancia.

Puedo tener un tipo de retorno genérico como Object, pero no quiero convertir el objeto devuelto en cada lugar al que llamo el método get instance.

El siguiente es el código: me sale un error de compilación en la línea c.cast (instancia);

No utilizamos la inyección spring / dependency, sino que intentamos implementar una clase común para encargarnos de crear todos los objetos singleton.

Gracias de antemano.

public class SingletonFactory {
    public static Map<String,Object> objectFactory = new HashMap<String, Object>();

    public static <T extends Object> T getInstance(Class<?> c){
    String key = c.toString();
    Object instance= objectFactory.get(key);
    if(instance == null){
        synchronized (c) {
            try{
                instance = c.newInstance();
                objectFactory.put(key, instance);
            }catch(IllegalAccessException | InstantiationException e){
                throw new RuntimeException("Exception while creating singleton instance for class : "+key+" - Exception Message : "+e);
            }
        }
    }
    return c.cast(instance);
}

}

1
Sanjeev 4 ene. 2019 a las 05:06

3 respuestas

La mejor respuesta

Primero, puedo señalar que <T extends Object> se puede reemplazar con solo <T> porque todo en Java, que involucra genéricos, debe ser un Objeto.

La segunda parte en la que estás muy cerca es Class<?> c. Eso dice que puede pasar cualquier clase y devolverá cualquier tipo T. c.cast(instance) se puede reemplazar con (T) instance si cree que se ve mejor pero, en realidad, hay una diferencia que entra en más detalles aquí: Java Class.cast () vs. operador de conversión.

El código final se ve así:

public class SingletonFactory {
    public static Map<String,Object> objectFactory = new HashMap<String, Object>();

    public static <T> T getInstance(Class<T> c){
        synchronized (c) {
            String key = c.toString();
            Object instance= objectFactory.get(key);
            if (instance == null) {
                try {
                    instance = c.newInstance();
                    objectFactory.put(key, instance);
                } catch(IllegalAccessException | InstantiationException e){
                    throw new RuntimeException("Exception while creating singleton instance for class : "+key+" - Exception Message : "+e);
                }
            }
            return c.cast(instance);
            // or
            return (T) instance;
        }
    }
}

Además, si realmente quisieras, puedes mantener todo en tu código original y enviar la instancia a T al final del método y debería funcionar. Lo único es que sus llamadas a métodos se verían como SingletonFactory.getInstance<Foo>(Foo.class) en lugar de SingletonFactory.getInstance(Foo.class). Esto se debe a Class<?> en su código original en lugar de Class<T>.

EDITAR: También cambié el código para sincronizar antes gracias @ Khan9797

6
retodaredevil 10 may. 2020 a las 17:15

En primer lugar, getInstance() no es seguro para subprocesos en términos de creación de la nueva instancia. Existe la posibilidad de que pueda crear varias instancias de una clase dada cuando varios subprocesos se ejecutan simultáneamente y variable == null es true.

public class SingletonFactory {
    private static Map<Class, Object> objectHolder = new HashMap<>();

    public <T> T getInstance(Class<T> clazz) {
        Object instance = objectHolder.get(clazz);
        if(instance == null) {
            synchronized (clazz) {
                if(instance == null) {
                    try{
                        instance = clazz.newInstance();
                        objectHolder.put(clazz, instance);
                    } catch (Exception e) {
                        // do some logging and maybe exit the program. Since the it would affect how whole system works.
                    }                            
                }
            }
        }

        return clazz.cast(instance);
    }  
}

Pero el mejor enfoque sería utilizar una inicialización ansiosa en lugar de una inicialización diferida. La razón por la que necesitamos sincronizar la sección crítica es que estamos creando esas instancias cuando la necesitamos. Entonces se convirtió en un problema readers-writers. Pero si solo hacemos el proceso de lectura, entonces no necesitamos sincronizar ya que no vamos a modificar su valor. Si conoce todas las clases que se van a crear y necesita acceso, podríamos inicializarlas en primer lugar. Para deshacernos del inconveniente de rendimiento de synchronized

public class SingletonFactory {
    private static Map<Class, Object> objectHolder = new HashMap<>();

    private Map<Class, Object> initialize() {
        Map<Class, Object> objectHolder = new HashMap<>();
        // create some objects and put it into Map
        return objectHolder;        

    }           

    public <T> T getInstance(Class<T> clazz) {
        Object instance = objectHolder.get(clazz);            
        return clazz.cast(instance);
    }  
}
0
Khan9797 10 may. 2020 a las 16:22

En primer lugar, debe sincronizar mucho antes, simplemente debe sincronizar el método, de lo contrario, puede crear una instancia adicional en una condición de carrera.

En segundo lugar, debe definir el genérico del método de esta manera:

public static <T> T getInstance(Class<? extends T> c)
4
Jai 4 ene. 2019 a las 02:21