Я читал статью о Синглтоне в Википедии и наткнулся на этот пример:
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. Я не буду приводить еще один пример, но надеюсь, что вы уловили идею. Пожалуйста, позвольте мне услышать ваши идеи о более элегантном способе выполнения того, что я пытаюсь сделать. Еще раз спасибо!
Ответы:
Я проясню свою точку зрения: синглтон с параметрами - это не синглтон .
Синглтон по определению - это объект, для которого требуется создать экземпляр не более одного раза. Если вы пытаетесь передать параметры конструктору, в чем смысл синглтона?
У вас есть два варианта. Если вы хотите, чтобы ваш синглтон был инициализирован некоторыми данными, вы можете загрузить его данными после создания экземпляра , например:
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
В любом случае создание экземпляра всегда будет без параметров. В противном случае ваш синглтон не является синглтоном.
источник
Context
).Я думаю, вам нужно что-то вроде фабрики для создания и повторного использования объектов с различными параметрами. Это может быть реализовано с помощью синхронизированного
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
можно также рассматривать (или использовать как) параметризованные синглтоны, хотя допускает только статические варианты с фиксированным числом.Однако, если вам нужно распределенное решение 1 , подумайте о решении для бокового кэширования. Например: EHCache, Terracotta и т. Д.
1 в смысле охвата нескольких виртуальных машин на нескольких компьютерах.
источник
Вы можете добавить настраиваемый метод инициализации, чтобы отделить создание экземпляра от получения.
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)
один раз, чтобы настроить его, например, при запуске приложения.источник
Вы также можете использовать шаблон 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(); }
источник
Утверждение « Синглтон с параметрами не является одноэлементным » не совсем корректно . Нам нужно проанализировать это с точки зрения приложения, а не с точки зрения кода.
Мы создаем одноэлементный класс для создания одного экземпляра объекта за один запуск приложения. Имея конструктор с параметром, вы можете повысить гибкость своего кода, чтобы изменять некоторые атрибуты вашего одноэлементного объекта каждый раз, когда вы запускаете приложение. Это не нарушение паттерна Singleton. Если посмотреть на это с точки зрения кода, это выглядит как нарушение.
Шаблоны проектирования помогают нам писать гибкий и расширяемый код, а не мешают нам писать хороший код.
источник
Используйте методы получения и установки, чтобы установить переменную и сделать конструктор по умолчанию закрытым. Затем используйте:
источник
Удивлен, что никто не упомянул, как создается / извлекается регистратор. Например, ниже показано, как извлекается регистратор Log4J .
// 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 } ...
источник
Модификация паттерна 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); } }
источник
finally { RInterfaceHL.rloopHandler = null; }
вgetInstance
, потому что статическая ссылка может привести к утечке памяти , если мы не будем осторожны. В вашем случае похоже, что это не проблема, но я могу представить сценарий, в котором переданный объект большой и используетсяRInterfaceHL
ctor только для получения некоторых значений, а не для сохранения ссылки на него.return SingletonHolder.INSTANCE
так же отлично подойдёт и вgetInstance
. Я не думаю, что здесь нужна инкапсуляция, потому что внешний класс уже знает внутренности внутреннего класса, они тесно связаны: он знает, чтоrloopHandler
перед вызовом нужен init. Кроме того, закрытый конструктор не имеет никакого эффекта, потому что закрытые данные внутреннего класса просто доступны внешнему классу.Причина, по которой вы не можете понять, как выполнить то, что пытаетесь сделать, вероятно, состоит в том, что то, что вы пытаетесь сделать, на самом деле не имеет смысла. Вы хотите вызывать
getInstance(x)
с разными аргументами, но всегда возвращать один и тот же объект? Какого поведения вы хотите, когда звоните,getInstance(2)
а затемgetInstance(5)
?Если вы хотите, чтобы один и тот же объект был другим, но его внутреннее значение было другим, а это единственный способ, которым он остается синглтоном, вам вообще не нужно заботиться о конструкторе; вы просто устанавливаете значение
getInstance()
на выходе объекта. Конечно, вы понимаете, что все ваши другие ссылки на синглтон теперь имеют другое внутреннее значение.С другой стороны, если вы хотите
getInstance(2)
иgetInstance(5)
возвращать разные объекты, вы не используете шаблон Singleton, вы используете шаблон Factory.источник
В вашем примере вы не используете синглтон. Обратите внимание, что если вы сделаете следующее (при условии, что Singleton.getInstance действительно статический):
Singleton obj1 = Singleton.getInstance(3); Singleton obj2 = Singleton.getInstance(4);
Тогда значение obj2.x равно 3, а не 4. Если вам нужно это сделать, сделайте его простым классом. Если количество значений небольшое и фиксированное, вы можете рассмотреть возможность использования файла
enum
. Если у вас возникла проблема с чрезмерным генерированием объектов (что обычно не так), вы можете рассмотреть возможность кэширования значений (и проверить источники или получить помощь с этим, поскольку очевидно, как создавать кеши без опасности утечек памяти).Вы также можете прочитать эту статью, так как синглтоны очень легко злоупотреблять.
источник
Еще одна причина, по которой синглтоны являются анти-шаблоном, заключается в том, что, если они написаны в соответствии с рекомендациями с использованием частного конструктора, их очень сложно создать подкласс и настроить для использования в определенных модульных тестах. Например, потребуется для поддержки устаревшего кода.
источник
Если вы хотите создать класс Singleton, служащий контекстом, хороший способ - иметь файл конфигурации и читать параметры из файла внутри instance ().
Если параметры, входящие в класс Singleton, получаются динамически во время выполнения вашей программы, просто используйте статическую HashMap, хранящую разные экземпляры в вашем классе Singleton, чтобы гарантировать, что для каждого параметра (ов) создается только один экземпляр.
источник
Это не совсем синглтон, но может решить вашу проблему.
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; } }
источник
Если мы возьмем проблему как «как сделать синглтон с состоянием», то нет необходимости передавать состояние в качестве параметра конструктора. Я согласен с сообщениями, которые инициализируют состояния или используют метод set после получения экземпляра singleton.
Другой вопрос: хорошо ли иметь синглтон с состоянием?
источник
Не могли бы мы сделать что-то вроде этого:
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; } }
источник
Несмотря на то, что некоторые могут утверждать, вот синглтон с параметрами в конструкторе
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 говорит:
которые соблюдаются в этом примере.
Почему бы не установить свойство напрямую? Это учебный пример, чтобы показать, как мы можем получить синглтон с конструктором с параметром, но в некоторых ситуациях он может быть полезен. Например, в случаях наследования, чтобы заставить синглтон установить некоторые свойства суперкласса.
источник
Боюсь публиковать это в качестве ответа, но я не понимаю, почему никто об этом не думает, может быть, этот ответ уже был дан, я просто не понял этого.
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()
каждый раз возвращается один и тот же экземпляр, я думаю, это может сработать. Если это сильно не так, я удалю, меня просто интересует эта тема.источник
Думаю, это обычная проблема. Разделение «инициализации» синглтона от «получения» синглтона может работать (в этом примере используется вариант блокировки с двойной проверкой).
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; } ... }
источник
Синглтон - это, конечно, «антипаттерн» (при условии определения статики с переменным состоянием).
Если вам нужен фиксированный набор объектов неизменяемых значений, то вам подойдут перечисления. Для большого, возможно, открытого набора значений вы можете использовать репозиторий той или иной формы - обычно на основе
Map
реализации. Конечно, когда вы имеете дело со статикой, будьте осторожны с потоками (либо синхронизируйте достаточно широко, либо используйтеConcurrentMap
либо проверку того, что другой поток не побил вас, либо используйте некоторую форму фьючерсов).источник
Синглтоны обычно считаются антишаблонами и не должны использоваться. Они не упрощают тестирование кода.
В любом случае синглтон с аргументом не имеет смысла - что произойдет, если вы напишете:
Singleton s = SingletonHolder.getInstance(1); Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException
Ваш синглтон также не является потокобезопасным, поскольку несколько потоков могут выполнять одновременные вызовы, в
getInstance
результате чего создается более одного экземпляра (возможно, с разными значениямиx
).источник