Синглтон с аргументами в Java - PullRequest
128 голосов
/ 27 июня 2009

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

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

Ответы [ 19 ]

158 голосов
/ 27 июня 2009

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

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

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

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

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

38 голосов
/ 27 июня 2009

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

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

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

10 голосов
/ 27 сентября 2016

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

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 голосов
/ 06 октября 2014

Вы также можете использовать шаблон 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();
}
7 голосов
/ 27 июня 2009

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

Singleton.getInstance().setX(value);
5 голосов
/ 18 октября 2015

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

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

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

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

4 голосов
/ 23 августа 2010

Модификация шаблона 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);
    }
}
3 голосов
/ 27 июня 2009

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

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

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

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

3 голосов
/ 27 июня 2009

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

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

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

...