Синглтон с аргументами в 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. Я не буду приводить еще один пример, но я надеюсь, что вы поняли идею. Пожалуйста, позвольте мне услышать ваши идеи для более элегантного способа выполнить то, что я пытаюсь сделать. Еще раз спасибо!
21 ответ
Я четко изложу свою точку зрения: синглтон с параметрами не является синглтоном.
По определению, синглтон - это объект, экземпляр которого вы хотите создать не более одного раза. Если вы пытаетесь передать параметры конструктору, какой смысл в синглтоне?
У вас есть два варианта. Если вы хотите, чтобы ваш синглтон был инициализирован с некоторыми данными, вы можете загрузить его с данными после создания экземпляра, например, так:
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
В любом случае, реализация всегда будет без параметров. В противном случае ваш синглтон не является синглтоном.
Я думаю, что вам нужно нечто вроде фабрики, чтобы объекты с различными параметрами создавались и использовались повторно. Это может быть реализовано с помощью синхронизированного 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 в смысле охвата нескольких виртуальных машин на нескольких компьютерах.
Вы можете добавить метод инициализации, чтобы отделить создание от получения.
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();
}
Удивило, что никто не упомянул, как создается / восстанавливается регистратор. Например, ниже показано, как извлекается 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 }
...
Используйте методы получения и установки, чтобы установить переменную и сделать конструктор по умолчанию закрытым. Тогда используйте:
Singleton.getInstance().setX(value);
Утверждение "синглтон с параметрами не является синглтоном" не совсем правильно. Нам нужно проанализировать это с точки зрения приложения, а не с точки зрения кода.
Мы создаем одноэлементный класс для создания одного экземпляра объекта за один запуск приложения. Имея конструктор с параметром, вы можете встроить в свой код гибкость, позволяющую изменять некоторые атрибуты вашего одноэлементного объекта при каждом запуске приложения. Это не нарушение паттерна синглтона. Это выглядит как нарушение, если вы видите это с точки зрения кода.
Шаблоны проектирования помогают нам писать гибкий и расширяемый код, а не мешать нам писать хороший код.
Модификация шаблона 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);
}
}
Если вы хотите создать класс Singleton, служащий в качестве контекста, хорошим способом является наличие файла конфигурации и чтение параметров из файла в instance().
Если параметры, питающие класс Singleton, получаются динамически во время работы вашей программы, просто используйте статический HashMap, хранящий различные экземпляры в вашем классе Singleton, чтобы гарантировать, что для каждого параметра (-ов) создается только один экземпляр.
Еще одна причина, по которой Singletons является анти-паттерном, заключается в том, что если они написаны в соответствии с рекомендациями, с закрытым конструктором, их очень трудно выделить на подкласс и настроить для использования в определенных модульных тестах. Например, потребуется поддерживать устаревший код.
В вашем примере вы не используете синглтон. Обратите внимание, что если вы сделаете следующее (при условии, что Singleton.getInstance действительно был статическим):
Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);
Тогда значения obj2.x равны 3, а не 4. Если вам нужно сделать это, сделайте его простым классом. Если число значений мало и фиксировано, вы можете рассмотреть возможность использования enum
, Если у вас есть проблемы с чрезмерным созданием объектов (что обычно не так), то вы можете рассмотреть значения кэширования (и проверить источники или получить помощь в этом, так как очевидно, как создавать кэши без риска утечек памяти).
Вы также можете прочитать эту статью, так как синглтоны могут быть очень легко использованы.
Причина, по которой вы не можете понять, как выполнить то, что вы пытаетесь сделать, заключается в том, что то, что вы пытаетесь сделать, на самом деле не имеет смысла. Вы хотите позвонить getInstance(x)
с разными аргументами, но всегда возвращать один и тот же объект? Какое поведение вы хотите, когда вы звоните getInstance(2)
а потом getInstance(5)
?
Если вы хотите, чтобы один и тот же объект, но его внутреннее значение было другим, что является единственным способом, которым он все еще является одиночным, тогда вам вообще не нужно заботиться о конструкторе; вы просто устанавливаете значение в getInstance()
на выходе объекта. Конечно, вы понимаете, что все ваши другие ссылки на синглтон теперь имеют другое внутреннее значение.
Если ты хочешь getInstance(2)
а также getInstance(5)
чтобы возвращать разные объекты, с другой стороны, вы не используете шаблон Singleton, вы используете шаблон Factory.
Не могли бы мы сделать что-то вроде этого:
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 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 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 установить некоторые свойства суперкласса.
Если мы воспринимаем проблему как "как создать синглтон с состоянием", то нет необходимости передавать состояние в качестве параметра конструктора. Я согласен с постами, которые инициализируют состояния или используют метод set после получения экземпляра 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;
}
}
Что-то добавить, если вы хотите, чтобы параметры были инициализированы только один раз и не должны быть перезаписаны, просто реализуйте проверку и исключение, если кто-то попытается инициализировать их снова. Бывший.:
public class Service {
private String host = null;
private String port = null;
private Service() {
}
private static class ServiceSingletonHolder {
private static final Service INSTANCE = new Service();
}
public static Service getInstance() {
return ServiceSingletonHolder.INSTANCE;
}
public void initilize(String host, String port) {
if (this.host != null && host != null) {
throw new IllegalArgumentException("host can not be overwritten");
}
if (this.port != null && port != null) {
throw new IllegalArgumentException("port can not be overwritten");
}
this.host = host;
this.port = port;
}
}
Я думаю, что это общая проблема. Отделение "инициализации" синглтона от "получения" синглтона может сработать (в этом примере используется вариант двойной проверки блокировки).
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
).