Синглтон с аргументами в Java

Я читал статью Синглтона в Википедии, и я наткнулся на этот пример:

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

В то время как мне очень нравится, как ведет себя этот Синглтон, я не вижу, как его адаптировать, чтобы включить аргументы в конструктор. Каков предпочтительный способ сделать это в Java? Должен ли я сделать что-то подобное?

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

Спасибо!


Изменить: Я думаю, что я начал бурю споров с моим желанием использовать Синглтон. Позвольте мне объяснить мою мотивацию и, надеюсь, кто-то может предложить лучшую идею. Я использую grid grid framework для выполнения задач параллельно. В общем, у меня есть что-то вроде этого:

// 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).
    }
}

Что происходит, так это то, что, хотя я просто передаю ссылку на мои данные на все задачи, когда задачи сериализуются, данные копируются снова и снова. То, что я хочу сделать, - это совместное использование объекта среди всех задач. Естественно, я мог бы изменить класс следующим образом:

// 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).
    }
}

Как вы можете видеть, даже здесь у меня проблема, связанная с прохождением другого пути к файлу, после первого прохождения ничего не значит. Вот почему мне нравится идея магазина, который был опубликован в ответах. Во всяком случае, вместо того, чтобы включать логику для загрузки файла в методе run, я хотел бы отвлечь эту логику в классе Singleton. Я не буду приводить еще один пример, но я надеюсь, что вы поймете эту идею. Пожалуйста, позвольте мне услышать ваши идеи для более элегантного способа выполнить то, что я пытаюсь сделать. Еще раз спасибо!

Ответ 1

Я хочу подчеркнуть: синглтон с параметрами не является одиночным.

Синтаксис, по определению, является объектом, который вы хотите создать не более одного раза. Если вы пытаетесь передать параметры конструктору, какова точка синглтона?

У вас есть два варианта. Если вы хотите, чтобы ваш singleton был инициализирован некоторыми данными, вы можете загрузить его с данными после создания экземпляра, например:

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

Если операция, которую выполняет ваш singleton, повторяется и каждый раз с различными параметрами, вы можете передать параметры главному методу:

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

В любом случае, инстанцирование всегда будет без параметров. В противном случае ваш синглтон не будет синглом.

Ответ 2

Я думаю, вам нужно что-то вроде factory для создания объектов с различными параметрами, созданными и повторно используемыми. Его можно реализовать с помощью синхронизированных HashMap или ConcurrentHashMap сопоставить параметр (a Integer для примера) с вашим параметризуемым классом "singleton".

Хотя вы можете перейти к тому, что вам следует использовать обычные, не-одиночные классы вместо этого (например, нужно использовать 10 000 различных параметризованных синглов).

Вот пример такого хранилища:

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

Чтобы продвинуть его еще дальше, Java enum можно также рассматривать (или использовать как) параметризованные синглтоны, хотя допускает только статические варианты с фиксированным числом.

Однако, если вам нужно распределенное решение 1 рассмотрите некоторое боковое кэширование. Например: EHCache, Terracotta и т.д.

1 в смысле охвата нескольких виртуальных машин на возможно нескольких компьютерах.

Ответ 3

Вы можете добавить настраиваемый метод инициализации, чтобы отделить создание от получения.

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

}

Затем вы можете вызвать Singleton.init(123) один раз, чтобы настроить его, например, при запуске вашего приложения.

Ответ 4

Вы также можете использовать шаблон Builder, если хотите показать, что некоторые параметры являются обязательными.

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

    }


}

Затем вы можете создать/создать/параметризовать его следующим образом:

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

Ответ 5

Используйте геттеры и сеттеры, чтобы установить переменную и сделать конструктор по умолчанию закрытым. Затем используйте:

Singleton.getInstance().setX(value);

Ответ 6

Удивлен, что никто не упоминал, как создается/извлекается журнал. Например, ниже показано, как Log4J logger извлекается.

// 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)

Существует несколько уровней косвенных функций, но ключевая часть ниже метод, который в значительной степени рассказывает о том, как это работает. Он использует хэш-таблицу для хранения выходных регистраторов, а ключ - от имени. Если регистратор не существует для имени имени, он использует factory для создания регистратора, а затем добавляет его в хеш-таблицу.

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

Ответ 7

" Синтаксис с параметрами не является одиночным. Оператор не совсем корректен. Нам нужно проанализировать это с точки зрения приложения, а не с точки зрения кода.

Мы строим класс singleton для создания одного экземпляра объекта в одном запуске приложения. Имея конструктор с параметром, вы можете создавать гибкость в своем коде, чтобы изменять некоторые атрибуты вашего объекта singleton каждый раз, когда вы запускаете приложение. Это не является нарушением шаблона Singleton. Это выглядит как нарушение, если вы видите это с точки зрения кода.

Шаблоны проектирования помогут нам написать гибкий и расширяемый код, чтобы не мешать нам писать хороший код.

Ответ 8

Модификация шаблона Singleton, в котором используется Инициализация Билла Пюга по идиоме спроса. Это потокобезопасность без накладных расходов на специализированные языковые конструкции (т.е. Волатильные или синхронизированные):

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

Ответ 9

Причина, по которой вы не можете понять, как выполнить то, что вы пытаетесь сделать, вероятно, заключается в том, что то, что вы пытаетесь сделать, на самом деле не имеет смысла. Вы хотите вызвать getInstance(x) с разными аргументами, но всегда возвращать тот же объект? Какое поведение вы хотите, когда вы вызываете getInstance(2), а затем getInstance(5)?

Если вы хотите, чтобы один и тот же объект, но для его внутреннего значения был другим, что является единственным способом, которым он по-прежнему является одиночным, тогда вам вообще не нужно заботиться о конструкторе; вы просто устанавливаете значение в getInstance() на выходе объекта. Конечно, вы понимаете, что все ваши другие ссылки на синглтон теперь имеют другое внутреннее значение.

Если вы хотите, чтобы getInstance(2) и getInstance(5) возвращали разные объекты, с другой стороны, вы не используете шаблон Singleton, вы используете шаблон Factory.

Ответ 10

В вашем примере вы не используете синглтон. Обратите внимание, что если вы выполните следующее (предполагая, что Singleton.getInstance фактически статично):

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

Тогда значения obj2.x равны 3, а не 4. Если вам нужно сделать это, сделайте его простым классом. Если количество значений невелико и фиксировано, вы можете использовать enum. Если у вас возникли проблемы с чрезмерной генерации объектов (что обычно не так), вы можете рассмотреть кеширование значений (и проверить источники или получить справку по этому поводу, так как очевидно, как создавать кеши без опасности утечки памяти).

Вы также можете захотеть прочитать эту статью, так как синглтоны могут быть очень легко использованы.

Ответ 11

Здесь обсуждается то, что плохо и хорошо о синглонах (со многими хорошими ссылками) здесь - http://c2.com/cgi/wiki?SingletonsAreEvil (не позволяйте название обманывает вас - это довольно беспристрастно)

Я бы предложил использовать инъекцию зависимостей для управления композицией вашего приложения вместо структурных шаблонов, таких как фабрики и singleton.

Ответ 12

Еще одна причина, по которой Singletons - это анти-шаблон, заключается в том, что если они написаны в соответствии с рекомендациями, с помощью частного конструктора, они очень сложны для подкласса и настраиваются для использования в некоторых модульных тестах. Требуется, например, для сохранения устаревшего кода.

Ответ 13

Если вы хотите создать класс Singleton, служащий в качестве контекста, хорошим способом является создание файла конфигурации и чтение параметров из файла внутри instance().

Если параметры, подающие класс Singleton, динамически загружаются во время работы вашей программы, просто используйте статический HashMap, хранящий разные экземпляры в вашем классе Singleton, чтобы гарантировать, что для каждого параметра (ов) создается только один экземпляр.

Ответ 14

Если взять проблему как "как сделать singleton с состоянием", тогда нет необходимости передавать состояние в качестве параметра конструктора. Я согласен с сообщениями, которые инициализируют состояния или используют метод set после получения экземпляра singleton.

Другой вопрос: хорошо ли иметь одноэлемент с состоянием?

Ответ 15

Не могли бы мы сделать что-то вроде этого:

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

Ответ 16

Несмотря на то, что некоторые утверждают, вот синглтон с параметрами в конструкторе

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
     */
}

Синтаксический шаблон говорит:

  • убедитесь, что существует только один экземпляр класса singleton
  • предоставляет глобальный доступ к этому экземпляру.

которые соблюдаются в этом примере.

Почему прямо не задано свойство? Это случай учебника, чтобы показать, как мы можем получить singleton с конструктором с параметром, но он может быть полезен в некоторых ситуациях. Например, в случаях наследования, чтобы заставить singleton установить некоторые свойства суперкласса.

Ответ 17

Это не совсем одноэлемент, но может быть что-то, что могло бы решить вашу проблему.

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

Ответ 18

Я боюсь опубликовать это как ответ, но я не понимаю, почему никто не думает об этом, может быть, этот ответ уже был дан, я просто не понял его.

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

Поскольку getInstance() каждый раз возвращает один и тот же экземпляр, я думаю, что это может сработать. Если это сильно не так, я удалю его, мне просто интересна эта тема.

Ответ 19

Я думаю, что это общая проблема. Отделение "инициализации" синглтона от "получения" синглтона может сработать (в этом примере используется вариант двойной проверки блокировки).

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

    ...
}

Ответ 20

Синглтон - это, конечно, "анти-шаблон" (предполагая определение статического состояния с переменным состоянием).

Если вам нужен фиксированный набор объектов неизменяемого значения, то перечисления - это путь. Для большого, возможно, открытого набора значений вы можете использовать репозиторий некоторой формы - обычно на основе реализации Map. Конечно, когда вы имеете дело со статикой, будьте осторожны с потоками (либо достаточно широко синхронизируйте, либо используйте ConcurrentMap, либо проверяя, что другой поток не избил вас, а использовал какую-то форму фьючерсов).

Ответ 21

Синглтоны обычно считаются анти-шаблоны и не должны использоваться. Они не делают код легким для тестирования.

Синглтон с аргументом не имеет никакого смысла - что произойдет, если вы написали:

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

Ваш синглтон также не потокобезопасен, поскольку несколько потоков могут выполнять одновременные вызовы getInstance, что приводит к созданию нескольких экземпляров (возможно, с разными значениями x).