1
votes

Singleton Factory - Implémentation à l'aide de Java 8

Je veux implémenter un modèle d'usine singleton générique où je passe la classe de l'objet requis en tant que paramètre et la classe d'usine devrait vérifier dans la carte s'il y a déjà un objet créé pour elle, si c'est le cas, retourner l'objet de la carte . Sinon, créez une nouvelle instance, placez-la dans la carte et renvoyez l'instance.

Je peux avoir un type de retour générique comme Object, mais je ne veux pas convertir l'objet retourné à chaque endroit où j'appelle l'instance get

Voici le code: j'obtiens une erreur de compilation à la ligne c.cast(instance);

Nous n'utilisons pas spring / injection de dépendances, mais en essayant d'implémenter une classe commune pour prendre soin de créer tous les objets singleton.

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


3 commentaires

vous vous attendez à renvoyer générique de la fonction, mais l'instance est un objet qui n'est pas générique


Ouais, essayez simplement de comprendre comment déclarer ma carte pour avoir un type générique. Est-il au moins possible de mettre en place une usine comme celle-ci. Je pense que je suis proche, mais je ne sais pas comment écrire ce code.


vous devriez changer Class en Class donc lorsque vous effectuez un casting, il lance l'instance en T


3 Réponses :


6
votes

Premièrement, je peux souligner que peut être remplacé par car tout en Java, impliquant des génériques, doit être un objet.

La deuxième partie sur laquelle vous êtes vraiment proche est la Classe > c . Cela dit que vous pouvez passer n'importe quelle classe et que le type T sera renvoyé. c.cast (instance) peut être remplacé par (T) instance si vous pensez que cela semble mieux mais, il y a en fait une différence qui entre plus en détail ici: Java Class.cast () vs opérateur de cast .

Le le code final ressemble à ceci:

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

De plus, si vous le souhaitez vraiment, vous pouvez tout conserver dans votre code d'origine et convertir l'instance en T au niveau de fin de la méthode et cela devrait fonctionner. La seule chose est que vos appels de méthode ressembleraient à SingletonFactory.getInstance (Foo.class) au lieu de SingletonFactory.getInstance (Foo.class) . C'est à cause de la Class> dans votre code d'origine au lieu de Class.

EDIT: J'ai également changé le code pour synchroniser plus tôt merci @ Khan9797


2 commentaires

Puisque vous utilisez l'initialisation paresseuse, je vous suggère d'utiliser le verrouillage double vérification. Il existe une possibilité pour deux threads de vérifier la valeur de l'instance pour null. Si la variable d'instance est nulle, ils essaient tous les deux de créer. Ainsi, vous finiriez par écraser le hashmap pour une clé donnée.


@ Khan9797 vous avez raison. J'ai modifié ma réponse pour la synchroniser plus tôt. Puis-je synchroniser sur l'objet de classe ou dois-je simplement synchroniser la méthode elle-même? Ou est-ce important? Si je le garde tel quel, en synchronisant sur la classe, est-ce que je devrais aussi avoir un verrou lors de l'édition du HashMap?



4
votes

Premièrement, vous devez synchroniser beaucoup plus tôt, vous devez simplement synchroniser la méthode, sinon vous pouvez créer une instance supplémentaire dans une condition de concurrence.

Deuxièmement, vous devez définir le générique de la méthode comme ceci: p >

public static <T> T getInstance(Class<? extends T> c)


3 commentaires

Je n'ai pas besoin de déplacer la synchronisation au niveau de la méthode - je souhaite synchroniser la classe uniquement lorsqu'une instance de cette classe n'est pas trouvée. Je ne pense pas qu'il soit nécessaire de verrouiller toute la méthode.


Deux threads peuvent regarder if (instance == null) et supposer que l'instance est nulle et créer deux objets


Pourquoi avez-vous besoin de Class c ? Dans HashMap, ils sont tous stockés en tant qu ' Object , pas T



1
votes

Tout d'abord, getInstance () n'est pas thread-safe en termes de création de la nouvelle instance. Il est possible que vous puissiez créer plusieurs instances d'une classe donnée lorsque plusieurs threads s'exécutent simultanément et que variable == null est true .

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

Mais la meilleure approche consisterait à utiliser une initialisation hâtive au lieu d'une initialisation paresseuse. La raison pour laquelle nous devons synchroniser la section critique est que nous créons ces instances lorsque nous en avons besoin. C'est donc devenu un problème de lecteurs-écrivains . Mais si nous ne faisons que le processus de lecture, nous n'avons pas besoin de synchroniser puisque nous n'allons pas modifier sa valeur. Si vous connaissez toutes les classes qui vont être créées et qui doivent être accédées, nous pourrions simplement les initialiser en premier lieu. Pour nous débarrasser de l'inconvénient des performances synchronisées

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


0 commentaires