Singleton avec des Arguments en Java

je lisais L'article de Singleton sur Wikipedia et je suis tombé sur cet exemple:

public class Singleton {
    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

bien que j'aime vraiment la façon dont ce Singleton se comporte, Je ne vois pas comment l'adapter pour intégrer des arguments au constructeur. Quelle est la meilleure façon de faire cela en Java? Aurais-je à faire quelque chose comme cela?

public class Singleton
{
    private static Singleton singleton = null;  
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public synchronized static Singleton getInstance(int x) {
        if(singleton == null) singleton = new Singleton(x);
        return singleton;
    }
}

Merci!


Edit: je crois que j'ai commencé une tempête de controverse avec mon désir D'utiliser Singleton. Permettez-moi d'expliquer ma motivation et j'espère que quelqu'un pourra suggérer une meilleure idée. J'utilise un cadre de calcul grid pour exécuter des tâches en parallèle. En général, j'ai quelque chose comme ceci:

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private final ReferenceToReallyBigObject object;

    public Task(ReferenceToReallyBigObject object)
    {
        this.object = object;
    }

    public void run()
    {
        // Do some stuff with the object (which is immutable).
    }
}

ce qui se passe, c'est que même si je passe simplement une référence à mes données à toutes les tâches, quand les tâches sont sérialisées, les données sont copiées encore et encore. Ce que je veux faire est de partager l'objet de toutes les tâches. Naturellement, je pourrais modifier la classe comme ceci:

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private static ReferenceToReallyBigObject object = null;

    private final String filePath;

    public Task(String filePath)
    {
        this.filePath = filePath;
    }

    public void run()
    {
        synchronized(this)
        {
            if(object == null)
            {
                ObjectReader reader = new ObjectReader(filePath);
                object = reader.read();
            }
        }

        // Do some stuff with the object (which is immutable).
    }
}

comme vous pouvez le voir, même ici j'ai le problème que passer un chemin de fichier différent signifie rien après le premier est passé. C'est pourquoi j'aime l'idée d'un magasin qui a été publié dans les réponses. Quoi qu'il en soit, plutôt que d'inclure la logique pour charger le fichier dans la méthode run, j'ai voulu abstraire cette logique dans une classe Singleton. Je ne donnerai pas encore un autre exemple, mais je j'espère que vous obtenez l'idée. S'il vous plaît laissez-moi entendre vos idées pour une façon plus élégante d'accomplir ce que j'essaie de faire. Merci encore une fois!

121
demandé sur Yuval Adam 2009-06-27 00:02:44

19 réponses

je vais être très clair: un singleton avec des paramètres n'est pas un singleton .

Un singleton, par définition, est un objet que vous voulez être instanciée qu'une seule fois. Si vous essayez d'alimenter le constructeur en paramètres, Quel est l'intérêt du singleton?

vous avez deux options. Si vous voulez que votre singleton soit initialisé avec quelques données, vous pouvez le charger avec les données après instanciation , comme ainsi:

SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data

si l'opération que votre singleton exécute est récurrente, et avec des paramètres différents à chaque fois, vous pourriez aussi bien passer les paramètres à la méthode principale en cours d'exécution:

SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution

Dans tous les cas, l'instanciation sera toujours paramètre moins. Sinon, votre singleton n'est pas un singleton.

147
répondu Yuval Adam 2009-06-26 20:18:32

je pense que vous avez besoin de quelque chose comme un usine pour avoir des objets avec divers paramètres instancié et réutilisé. Il peut être implémenté en utilisant un HashMap ou ConcurrentHashMap synchronisé et en cartographiant un paramètre (un Integer pour un exemple) à votre classe paramétrable "singleton".

bien que vous pourriez arriver au point où vous devriez utiliser des classes régulières, non-singleton à la place (par exemple nécessitant 10.000 autrement paramétré Singleton.)

voici un exemple pour un tel magasin:

public final class UsefulObjFactory {

    private static Map<Integer, UsefulObj> store =
        new HashMap<Integer, UsefulObj>();

    public static final class UsefulObj {
        private UsefulObj(int parameter) {
            // init
        }
        public void someUsefulMethod() {
            // some useful operation
        }
    }

    public static UsefulObj get(int parameter) {
        synchronized (store) {
            UsefulObj result = store.get(parameter);
            if (result == null) {
                result = new UsefulObj(parameter);
                store.put(parameter, result);
            }
            return result;
        }
    }
}

pour le pousser encore plus loin, le Java enum s peut également être considéré (ou utilisé comme) des singletons paramétrés, tout en n'autorisant qu'un nombre fixe de variantes statiques.

cependant, si vous avez besoin d'une solution distribuée 1 , envisager une solution de cache latéral. Par exemple: EHCache, Terracotta, etc.

1 dans le sens de couvrir plusieurs VM sur probablement plusieurs ordinateurs.

37
répondu akarnokd 2017-06-20 05:32:17

vous pouvez également utiliser le modèle Builder si vous voulez montrer que certains paramètres sont obligatoires.

    public enum EnumSingleton {

    INSTANCE;

    private String name; // Mandatory
    private Double age = null; // Not Mandatory

    private void build(SingletonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    // Static getter
    public static EnumSingleton getSingleton() {
        return INSTANCE;
    }

    public void print() {
        System.out.println("Name "+name + ", age: "+age);
    }


    public static class SingletonBuilder {

        private final String name; // Mandatory
        private Double age = null; // Not Mandatory

        private SingletonBuilder(){
          name = null;
        }

        SingletonBuilder(String name) {
            this.name = name;
        }

        public SingletonBuilder age(double age) {
            this.age = age;
            return this;
        }

        public void build(){
            EnumSingleton.INSTANCE.build(this);
        }

    }


}

alors vous pourriez créer / instantiate/parametrized it comme suit:

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}
8
répondu gerardnico 2014-10-06 13:54:18

utilise getters et setters pour définir la variable et rendre le constructeur par défaut privé. Puis utiliser:

Singleton.getInstance().setX(value);
7
répondu AlbertoPL 2009-06-26 20:07:30

surpris que personne n'ait mentionné comment un logger est créé/récupéré. Par exemple, ci-dessous montre comment Logger Log4J est récupéré.

// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)

il y a quelques niveaux d'indirectes, mais la partie clé est en dessous de méthode qui dit à peu près tout sur la façon dont il fonctionne. Il utilise une table de hachage pour stocker les loggers sortant et la clé est dérivée de nom. Si l'enregistreur n'existe pas de donner un nom, il utilise un usine de créer de l'enregistreur et l'ajoute à la table de hachage.

69   Hashtable ht;
...
258  public
259  Logger getLogger(String name, LoggerFactory factory) {
260    //System.out.println("getInstance("+name+") called.");
261    CategoryKey key = new CategoryKey(name);
262    // Synchronize to prevent write conflicts. Read conflicts (in
263    // getChainedLevel method) are possible only if variable
264    // assignments are non-atomic.
265    Logger logger;
266
267    synchronized(ht) {
268      Object o = ht.get(key);
269      if(o == null) {
270        logger = factory.makeNewLoggerInstance(name);
271        logger.setHierarchy(this);
272        ht.put(key, logger);
273        updateParents(logger);
274        return logger;
275      } else if(o instanceof Logger) {
276        return (Logger) o;
277      } 
...
5
répondu wanghq 2015-10-17 23:56:46

Modification of Singleton pattern that uses initialization on demand holder idiom . Il s'agit d'un thread sûr sans la charge des constructions langagières spécialisées (c'est-à-dire volatiles ou synchronisées):

public final class RInterfaceHL {

    /**
     * Private constructor prevents instantiation from other classes.
     */
    private RInterfaceHL() { }

    /**
     * R REPL (read-evaluate-parse loop) handler.
     */
    private static RMainLoopCallbacks rloopHandler = null;

    /**
     * SingletonHolder is loaded, and the static initializer executed, 
     * on the first execution of Singleton.getInstance() or the first 
     * access to SingletonHolder.INSTANCE, not before.
     */
    private static final class SingletonHolder {

        /**
         * Singleton instance, with static initializer.
         */
        private static final RInterfaceHL INSTANCE = initRInterfaceHL();

        /**
         * Initialize RInterfaceHL singleton instance using rLoopHandler from
         * outer class.
         * 
         * @return RInterfaceHL instance
         */
        private static RInterfaceHL initRInterfaceHL() {
            try {
                return new RInterfaceHL(rloopHandler);
            } catch (REngineException e) {
                // a static initializer cannot throw exceptions
                // but it can throw an ExceptionInInitializerError
                throw new ExceptionInInitializerError(e);
            }
        }

        /**
         * Prevent instantiation.
         */
        private SingletonHolder() {
        }

        /**
         * Get singleton RInterfaceHL.
         * 
         * @return RInterfaceHL singleton.
         */
        public static RInterfaceHL getInstance() {
            return SingletonHolder.INSTANCE;
        }

    }

    /**
     * Return the singleton instance of RInterfaceHL. Only the first call to
     * this will establish the rloopHandler.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @return RInterfaceHL singleton instance
     * @throws REngineException
     *             if REngine cannot be created
     */
    public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler)
            throws REngineException {
        RInterfaceHL.rloopHandler = rloopHandler;

        RInterfaceHL instance = null;

        try {
            instance = SingletonHolder.getInstance();
        } catch (ExceptionInInitializerError e) {

            // rethrow exception that occurred in the initializer
            // so our caller can deal with it
            Throwable exceptionInInit = e.getCause();
            throw new REngineException(null, exceptionInInit.getMessage());
        }

        return instance;
    }

    /**
     * org.rosuda.REngine.REngine high level R interface.
     */
    private REngine rosudaEngine = null;

    /**
     * Construct new RInterfaceHL. Only ever gets called once by
     * {@link SingletonHolder.initRInterfaceHL}.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @throws REngineException
     *             if R cannot be loaded.
     */
    private RInterfaceHL(RMainLoopCallbacks rloopHandler)
            throws REngineException {

        // tell Rengine code not to die if it can't
        // load the JRI native DLLs. This allows
        // us to catch the UnsatisfiedLinkError
        // ourselves
        System.setProperty("jri.ignore.ule", "yes");

        rosudaEngine = new JRIEngine(new String[] { "--no-save" }, rloopHandler);
    }
}
4
répondu tekumara 2016-06-14 13:19:20

vous pouvez ajouter une méthode d'initialisation pour séparer l'instanciation de getting.

public class Singleton {
    private static Singleton singleton = null;
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public static Singleton getInstance() {
        if(singleton == null) {
            throw new AssertionError("You have to call init first");
        }

        return singleton;
    }

    public synchronized static Singleton init(int x) {
        if (singleton != null)
        {
            // in my opinion this is optional, but for the purists it ensures
            // that you only ever get the same instance when you call getInstance
            throw new AssertionError("You already initialized me");
        }

        singleton = new Singleton(x);
        return singleton;
    }

}

puis vous appelez Singleton.init(123) une fois de quelque part pour le configurer, dans votre démarrage d'application par exemple.

4
répondu miguel 2017-01-05 22:40:13

la raison pour laquelle vous ne pouvez pas comprendre comment accomplir ce que vous essayez de faire est probablement que ce que vous essayez de faire n'a pas vraiment de sens. Vous voulez appeler getInstance(x) avec des arguments différents, mais toujours le même objet? Quel comportement voulez-vous quand vous appelez getInstance(2) et ensuite getInstance(5) ?

si vous voulez que le même objet mais que sa valeur interne soit différente, ce qui est la seule façon pour que ce soit toujours un singleton, alors vous ne besoin de se soucier du constructeur du tout; vous venez de définir la valeur dans getInstance() sur la sortie de l'objet. Bien sûr, vous comprenez que toutes vos autres références au singleton ont maintenant une valeur interne différente.

si vous voulez getInstance(2) et getInstance(5) pour rendre différents objets, d'un autre côté, vous n'utilisez pas le modèle Singleton, vous utilisez le modèle D'usine.

3
répondu chaos 2009-06-26 20:07:56

dans votre exemple, vous n'utilisez pas un singleton. Remarquez que si vous faites ce qui suit (en supposant que le Singleton.gettinstance était en fait statique):

Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);

puis l'obj2.les valeurs de x sont 3, Pas 4. Si vous avez besoin de faire cela, faites-en une classe simple. Si le nombre de valeurs est petit et fixe, vous pouvez envisager d'utiliser un enum . Si vous avez des problèmes avec la génération excessive d'objet( ce qui n'est généralement pas le cas), alors vous pouvez envisager la mise en cache valeurs (et vérifier les sources ou obtenir de l'aide avec cela, car il est évident comment construire des caches sans le danger de fuites de mémoire).

vous pourriez aussi vouloir lire cet article comme Singleton peut être très facilement surutilisé.

3
répondu Kathy Van Stone 2009-06-26 20:15:27

une autre raison pour laquelle les Singletons sont un anti-pattern est que s'ils sont écrits selon des recommandations, avec le constructeur privé, ils sont très difficiles à classer et configurer à utiliser dans certains tests unitaires. Serait nécessaire, par exemple, pour la mise à jour du code d'héritage.

3
répondu JosefB 2010-10-06 15:25:43

si nous prenons le problème comme" comment faire singleton avec l'État", alors il n'est pas nécessaire de passer l'état comme paramètre de constructeur. Je suis d'accord avec les messages qui initialisent les états ou en utilisant la méthode set après avoir obtenu l'instance singleton.

une autre question Est: est-il bon d'avoir singleton avec state?

1
répondu user3014901 2015-03-08 16:14:49

" Un singleton avec des paramètres n'est pas un singleton " la déclaration est pas correct . Nous devons l'analyser du point de vue de l'application plutôt que du point de vue du code.

nous construisons la classe singleton pour créer une instance unique d'un objet en un seul passage d'application. En ayant un constructeur avec paramètre, vous pouvez construire de la flexibilité dans votre code pour changer certains attributs de votre objet singleton chaque fois que vous exécutez l'application. Ce n'est pas une violation de Singleton pattern. Cela ressemble à une violation si vous voyez cela du point de vue du code.

les Modèles de conception sont là pour nous aider à écrire du code flexible et extensible, pas pour nous empêcher d'écrire du bon code.

1
répondu Vinod Nalla 2016-04-19 11:30:02

si vous voulez créer une classe Singleton servant de contexte, une bonne façon est d'avoir un fichier de configuration et de lire les paramètres à partir du fichier inside instance().

si les paramètres alimentant la classe Singleton sont obtenus dynamiquement pendant l'exécution de votre programme, utilisez simplement un HashMap statique stockant différentes instances dans votre classe Singleton pour vous assurer que pour chaque paramètre(s), une seule instance est créée.

1
répondu user3025839 2017-06-11 10:39:30

Ce n'est pas un singleton, mais peut-être quelque chose qui pourrait résoudre votre problème.

public class KamilManager {

  private static KamilManager sharedInstance;

  /**
   * This method cannot be called before calling KamilManager constructor or else
   * it will bomb out.
   * @return
   */
  public static KamilManager getInstanceAfterInitialized() {
    if(sharedInstance == null)
        throw new RuntimeException("You must instantiate KamilManager once, before calling this method");

    return sharedInstance;
}

  public KamilManager(Context context, KamilConfig KamilConfig) {
    //Set whatever you need to set here then call:
  s  haredInstance = this;
  }
}
0
répondu Kamilski81 2014-11-28 21:25:33

ne pourrions-nous pas faire quelque chose comme ça:

public class Singleton {

    private int x;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(int x) {
        Singleton instance = SingletonHolder.INSTANCE;
        instance.x = x;
        return instance;
    }
}
0
répondu Ionut Negru 2015-12-04 12:38:30

malgré ce que certains peuvent affirmer, voici un singleton avec des paramètres dans le constructeur

public class Singleton {

    private static String aParameterStored;

    private static final Singleton instance = new Singleton("Param to set");

    private Singleton() {
        // do nothing
    }

    private Singleton(String param) {
        aParameterStored = param;
    }

    public static Singleton getInstance() {
        return instance;
    }

    /*
     * ... stuff you would like the singleton do
     */
}

Le pattern singleton dire :

  • s'assurer qu'une seule instance de la classe singleton n'existe jamais
  • fournit un accès global à cette instance.

qui sont respectées avec cet exemple.

Pourquoi ne pas définir directement la propriété ? C'est manuel cas pour montrer comment nous pouvons obtenir un singleton ayant constructeur avec paramètre mais il pourrait être utile dans certaines situations. Par exemple, dans les cas d'héritage pour forcer le singleton à mettre certaines propriétés de superclasse.

0
répondu Zou 2017-02-01 16:00:57

Singleton est, bien sûr, un "anti-pattern" (en supposant une définition d'un statique à l'état variable).

si vous voulez un ensemble fixe d'objets de valeur immuable, alors les énums sont la voie à suivre. Pour un grand ensemble de valeurs ouvert, vous pouvez utiliser un dépôt d'une certaine forme - généralement basé sur une implémentation Map . Bien sûr, lorsque vous avez affaire à la statique, soyez prudent avec le filetage (soit synchroniser suffisamment largement ou utiliser un ConcurrentMap soit en vérifiant qu'un autre fil ne vous a pas battu, soit en utilisant une forme de futures).

-1
répondu Tom Hawtin - tackline 2009-06-26 20:23:13

je pense que c'est un problème commun. Séparer l '" initialisation "du singleton de l '" get " du singleton peut fonctionner (cet exemple utilise une variante du verrouillage double vérifié).

public class MySingleton {

    private static volatile MySingleton INSTANCE;

    @SuppressWarnings("UnusedAssignment")
    public static void initialize(
            final SomeDependency someDependency) {

        MySingleton result = INSTANCE;

        if (result != null) {
            throw new IllegalStateException("The singleton has already "
                    + "been initialized.");
        }

        synchronized (MySingleton.class) {
            result = INSTANCE;

            if (result == null) {
                INSTANCE = result = new MySingleton(someDependency);
            } 
        }
    }

    public static MySingleton get() {
        MySingleton  result = INSTANCE;

        if (result == null) {
            throw new IllegalStateException("The singleton has not been "
                    + "initialized. You must call initialize(...) before "
                    + "calling get()");
        }

       return result;
    }

    ...
}
-1
répondu Michael Andrews 2018-03-29 17:53:06
Les Singletons

sont généralement considérés comme anti-patterns et ne devraient pas être utilisés. Ils ne rendent pas le code facile à tester.

un simple avec un argument n'a aucun sens de toute façon - ce qui se passerait si vous écriviez:

Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException

votre singleton est aussi non thread-safe car plusieurs threads peuvent faire des appels simultanés à getInstance résultant en plus d'une instance étant créé (éventuellement avec des valeurs différentes de x ).

-4
répondu oxbow_lakes 2009-06-26 20:10:53