web-dev-qa-db-fra.com

Singleton avec arguments dans 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 le comportement de Singleton, je ne vois pas comment l'adapter pour incorporer des arguments au constructeur. Quel est le moyen préféré de le faire en Java? Devrais-je faire quelque chose comme ça?

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 pense que j'ai commencé une tempête de controverse avec mon désir d'utiliser Singleton. Laissez-moi expliquer ma motivation et j'espère que quelqu'un pourra vous suggérer une meilleure idée. J'utilise un framework de calcul en grille pour exécuter des tâches en parallèle. En général, j'ai quelque chose comme ça:

// 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, lorsque les tâches sont sérialisées, les données sont copiées à plusieurs reprises. Ce que je veux faire, c'est partager l'objet entre toutes les tâches. Naturellement, je pourrais modifier la classe comme suit:

// 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 constater, même ici, le problème suivant est que transmettre un chemin de fichier différent ne signifie rien après le passage du premier. C'est pourquoi j'aime bien l'idée d'un magasin qui a été posté dans les réponses. Quoi qu'il en soit, plutôt que d'inclure la logique de chargement du fichier dans la méthode d'exécution, j'ai voulu résumer cette logique dans une classe Singleton. Je ne donnerai pas un autre exemple, mais j'espère que vous aurez l'idée. S'il vous plaît, laissez-moi entendre vos idées pour une manière plus élégante d'accomplir ce que j'essaie de faire. Merci encore!

128
Scott

Je vais expliquer très clairement: n singleton avec des paramètres n'est pas un singleton.

Un singleton, par définition, est un objet que vous ne voulez pas instancier plus d’une fois. Si vous essayez de fournir des paramètres au constructeur, à quoi sert le singleton?

Vous avez deux options. Si vous souhaitez que votre singleton soit initialisé avec certaines données, vous pouvez le charger avec des données après l'instanciation , comme suit:

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

Si l'opération que votre singleton effectue est récurrente et avec des paramètres différents à chaque fois, vous pouvez également transmettre 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 sans paramètre. Sinon, votre singleton n'est pas un singleton.

160
Yuval Adam

Je pense que vous avez besoin de quelque chose comme un factory pour avoir des objets avec divers paramètres instanciés et réutilisés. Cela pourrait être implémenté en utilisant un HashMap ou ConcurrentHashMap synchronisé un paramètre (un Integer à titre d'exemple) avec votre classe paramétrable 'singleton'.

Bien que vous puissiez en arriver au point où vous devriez utiliser à la place des classes ordinaires, non-singleton (par exemple, nécessitant 10.000 singleton paramétrés différemment).

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 aller encore plus loin, les Java enums peuvent également être considérés (ou utilisés comme) des singletons paramétrés, bien que n'autorisant qu'un nombre fixe de variantes statiques.

Cependant, si vous avez besoin d’un1 solution, envisager une solution de mise en cache latérale. Par exemple: EHCache, Terracotta, etc.

1 dans le sens d'étendre plusieurs machines virtuelles sur probablement plusieurs ordinateurs.

39
akarnokd

Vous pouvez également utiliser le modèle de générateur si vous souhaitez indiquer 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);
        }

    }


}

Ensuite, vous pourriez créer/instancier/paramétrer comme suit:

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}
10
gerardnico

Vous pouvez ajouter une méthode d’initialisation afin de séparer l’instanciation de l’obtention.

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

}

Ensuite, vous pouvez appeler Singleton.init(123) une fois pour le configurer, par exemple au démarrage de votre application.

10
miguel

Utilisez les getters et les setters pour définir la variable et rendre le constructeur par défaut privé. Alors utilisez:

Singleton.getInstance().setX(value);
7
AlbertoPL

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

Nous construisons une classe singleton pour créer une instance unique d'un objet dans une exécution d'application. En ayant un constructeur avec paramètre, vous pouvez créer de la flexibilité dans votre code pour modifier certains attributs de votre objet singleton à chaque fois que vous exécutez votre application. Ce n'est pas une violation du modèle Singleton. 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, et non pour nous empêcher d'écrire un bon code.

5
Vinod Nalla

Surpris que personne ne mentionne comment un enregistreur est créé/récupéré. Par exemple, ci-dessous montre comment log4J logger 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 de indirections, mais la partie clé est en dessous de méthode , qui en dit à peu près tout sur son fonctionnement. Il utilise une table de hachage pour stocker les enregistreurs existants et la clé est dérivée de nom. Si le consignateur n'existe pas pour un nom donné, il utilise une fabrique pour le créer, puis 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
wanghq

Modification du motif Singleton qui utilise initialisation de Bill Pugh sur l'idiome du détenteur de la demande . Ceci est thread-safe sans la surcharge de constructions de langage spécialisées (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
tekumara

Une autre raison pour laquelle les singletons sont un anti-modèle est que, s'ils sont écrits conformément aux recommandations, avec un constructeur privé, ils sont très difficiles à sous-classer et à configurer pour une utilisation dans certains tests unitaires. Serait nécessaire pour maintenir le code hérité, par exemple.

3
JosefB

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 vous renvoyez toujours le même objet? Quel comportement voulez-vous lorsque vous appelez getInstance(2) puis getInstance(5)?

Si vous voulez le même objet mais que sa valeur interne soit différente, ce qui est le seul moyen de conserver un singleton, vous n'avez pas besoin de vous soucier du constructeur. vous venez de définir la valeur dans getInstance() à la sortie de l'objet. Bien entendu, vous comprenez que toutes vos autres références au singleton ont maintenant une valeur interne différente.

Si vous voulez que getInstance(2) et getInstance(5) renvoient des objets différents, par contre, vous n'utilisez pas le modèle Singleton, vous utilisez le modèle Usine.

3
chaos

Dans votre exemple, vous n'utilisez pas de singleton. Notez que si vous procédez comme suit (en supposant que Singleton.getInstance était réellement statique):

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

Alors les valeurs de obj2.x sont 3 et non 4. Si vous devez le faire, faites-en une classe simple. Si le nombre de valeurs est petit et fixe, vous pouvez envisager d'utiliser un enum. Si vous rencontrez des problèmes avec la génération excessive d'objets (ce qui n'est généralement pas le cas), vous pouvez alors envisager de mettre en cache des valeurs (et consulter des sources ou obtenir de l'aide, car il est évident de créer des caches sans risque de fuites de mémoire).

Vous voudrez peut-être aussi lire cet article , car les singletons peuvent être très facilement surutilisés.

3
Kathy Van Stone

Si vous voulez créer une classe Singleton servant de contexte, un bon moyen consiste à disposer d'un fichier de configuration et à lire les paramètres à partir du fichier inside instance ().

Si les paramètres alimentant la classe Singleton sont obtenus de manière dynamique pendant l'exécution de votre programme, utilisez simplement un HashMap statique stockant différentes instances dans votre classe Singleton pour vous assurer qu'une seule instance est créée pour chaque paramètre.

2
user3025839

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;
    }
}
1
Ionut Negru

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

Une autre question est: est-ce bien d’avoir singleton avec state?

1
user3014901

En dépit de 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 motif singleton dit:

  • s'assurer que seulement une instance de la classe singleton existe jamais
  • fournir un accès global à cette instance.

qui sont respectés avec cet exemple.

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

1
Zou

Ce n'est pas tout à fait 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
Kamilski81

J'ai peur de poster ceci comme réponse, mais je ne comprends pas pourquoi personne n'y pense, peut-être que cette réponse a également été donnée, je ne comprenais tout simplement pas.

public class example  {
    private volatile static example instance;

    private String string;
    private int iInt = -1; //any number you know you don't want to use here

  private example() {

    //In case someone uses the private method to create a new Instance
    if (instance != null){
      throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    }
  }

  public synchronized static example getIsntance(){
    if(instance == null){
      instance = new example();
    }
    return instance;
  }

public void methodDoingWork(){
    if(checkInit()){
      //DoSome
    }
  }

  private boolean checkInit(){
    boolean filled = (this.string != null) && (this.iInt != -1);
    return filled;
  }

  public void setString(String string) {
    if(this.string == null){
      this.string = string;
    }else{
      throw new RuntimeException("You try to override an already setValue"); 
    }
  }

  public void setiInt(int iInt) {
    if(this.iInt == -1){
      this.iInt = iInt;
    }else{
      throw new RuntimeException("You try to override an already setValue");
    }
  }
}

Puisque la getInstance() renvoie la même instance à chaque fois, je pense que cela pourrait fonctionner. Si cela ne va pas trop, je vais le supprimer, je suis simplement intéressé par ce sujet.

0
HydroHeiperGen