it-swarm.com.ru

Синглтон с аргументами в 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;
    }
}

Спасибо!


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

// 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. Я не буду приводить еще один пример, но я надеюсь, что вы поняли идею. Пожалуйста, позвольте мне услышать ваши идеи для более элегантного способа выполнить то, что я пытаюсь сделать. Еще раз спасибо!

128
Scott

Я четко изложу свою точку зрения: синглтон с параметрами не является синглтоном.

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

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

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

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

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

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

158
Yuval Adam

Я думаю, что вам нужно что-то вроде factory, чтобы объекты с различными параметрами создавались и использовались повторно. Это может быть реализовано путем использования синхронизированного HashMap или ConcurrentHashMap сопоставления параметра (например, 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 enums также можно рассматривать (или использовать как) параметризованные синглтоны, хотя допускаются только фиксированные числа статических вариантов.

Однако, если вам нужен распределенный1 Решение, рассмотрим некоторые латеральные решения для кэширования. Например: EHCache, терракота и т.д.

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

38
akarnokd

Вы также можете использовать шаблон 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();
}
10
gerardnico

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

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) один раз, чтобы настроить его, например, при запуске приложения.

10
miguel

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

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

Удивлен, что никто не упомянул, как создается/восстанавливается регистратор. Например, ниже показано, как получить 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)

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

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

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

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

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

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

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

4
Vinod Nalla

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

3
JosefB

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

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

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

3
chaos

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

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

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

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

3
Kathy Van Stone

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

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

2
user3025839

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

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

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

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

1
user3014901

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

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 установить некоторые свойства суперкласса.

1
Zou

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

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