168 votes

Singleton avec arguments en Java

Je lisais l'article 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 beaucoup le comportement de ce Singleton, je ne vois pas comment l'adapter pour intégrer des arguments au constructeur. Quelle est la meilleure façon de le faire en Java ? Devrais-je faire quelque chose comme ceci ?

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 déclenché 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 suggérer une meilleure idée. J'utilise un cadre de calcul en grille 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 ne fais que passer 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 encore et encore. 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 voir, même ici, le problème est que le fait de passer un chemin d'accès différent ne signifie rien une fois que le premier est passé. C'est pourquoi j'aime l'idée d'un magasin qui a été affiché 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, je voulais abstraire cette logique dans une classe Singleton. Je ne vais pas fournir un autre exemple, mais j'espère que vous comprenez l'idée. N'hésitez pas à me faire part de vos idées sur une manière plus élégante d'accomplir ce que j'essaie de faire. Merci encore !

202voto

Yuval Adam Points 59423

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 qui ne doit être instancié qu'une seule fois. Si vous essayez de fournir des paramètres au constructeur, quel est l'intérêt du singleton ?

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

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 tout 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 sans paramètre. Sinon, votre singleton n'est pas un singleton.

44voto

kd304 Points 8369

Je pense que vous avez besoin de quelque chose comme un usine d'avoir des objets avec divers paramètres instanciés et réutilisés. On peut l'implémenter en utilisant une méthode synchronisée HashMap o ConcurrentHashMap mettre en correspondance un paramètre (un Integer pour un exemple) à votre classe paramétrable "singleton".

Bien que vous puissiez arriver à un point où vous devriez utiliser des classes régulières, non singleton à la place (par exemple, avoir besoin de 10.000 singleton paramétrés différemment).

Voici un exemple d'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, le Java enum peuvent également être considérés (ou utilisés) comme des singletons paramétrés, bien que ne permettant qu'un nombre fixe de variantes statiques.

Cependant, si vous avez besoin d'une distribution 1 envisagez une solution de mise en cache latérale. Par exemple : EHCache, Terracotta, etc.

1 dans le sens où il s'agit de couvrir plusieurs VM sur probablement plusieurs ordinateurs.

41voto

miguel Points 1537

Vous pouvez ajouter une méthode d'initialisation configurable 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 dans le démarrage de votre application.

15voto

gerardnico Points 365

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/instaurer/paramétrer comme suit :

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

11voto

wanghq Points 744

Je suis surpris que personne n'ait mentionné comment un logger est créé/récupéré. Par exemple, l'exemple ci-dessous montre comment Journaliste 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 plusieurs niveaux d'indirections, mais la partie essentielle est ci-dessous. méthode qui dit à peu près tout sur son fonctionnement. Il utilise une table de hachage pour stocker les loggers sortants et la clé est dérivée du nom. Si le logger n'existe pas pour un nom donné, il utilise une fabrique pour créer le logger et l'ajoute ensuite à 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      } 
...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X