Singleton - зло?
Сам обильно их не использовал, на памяти только один раз - и тот чисто для практики. Проект расширять не пришлось, поэтому проблем и не было. А вообще, я сам отрицательно отношусь к сему шаблону и хотелось бы узнать чужое мнение и опыт по этому вопросу...
p.s. недавно на этом форуме Green написал статейку по тому как с ними бороться
Я не уверен, что передача в конструктор критичных параметров является хорошей идеей. Вот например передам я созданой планете NULL вместо солнца. И что тогда? Использовать прямо в конструкторе сомнительный код вроде:
Не хорошо если надо обрабатывать ету ошибку. В даном же случае на лицо не RunTime ошибка, а ошибка программирования. Тоесть можно выплюнуть сообщение и прекратить роботу программы. Но как я уже сказал ето только в том случае если передача в конструктор NULL не должна произойти никогда.
Если такое может случится и ету ошибку надо обработать и продолжить програму то как сказал vAC делаем класс СолнечнаяСистема. Делаем в нем метод
createPlaneta(параметры планеты).
Солнечная система проверяет есть ли в ней звезда. Теперь проверка не в конструкторе.
Можно сделать статический метод класса Планета и покласть на него функцию Builder-а
static Planeta createPlaneta(Солнце, параметры планеты)
Как видиш легко обходимся другими паттернами.
Нет, это ссылка на экземпляр солнца.
В терминах твоего примера:
: _sun(sun)
{}
void СPlanet::func()
{
_sun.getMass(); // как пример обращения к "солнцу"
}
2 Rebbit
Я не уверен, что передача в конструктор критичных параметров является хорошей идеей. Вот например передам я созданой планете NULL вместо солнца. И что тогда? Использовать прямо в конструкторе сомнительный код вроде:
?? Просто, насколько я себе представляю - передача в конструторе критичных параметров - передает управление объектом самому объекту, а это не есть хорошо.
Брр... ничего не понял.
Rebbit просто предлагал передавать ссылку на солнце в конструктор планеты, т.е. тоже самое, что в моем пример в этом посте.
Если такое может случится и ету ошибку надо обработать и продолжить програму то как сказал vAC делаем класс СолнечнаяСистема. Делаем в нем метод
createPlaneta(параметры планеты).
Солнечная система проверяет есть ли в ней звезда. Теперь проверка не в конструкторе.
Можно сделать статический метод класса Планета и покласть на него функцию Builder-а
static Planeta createPlaneta(Солнце, параметры планеты)
Как видиш легко обходимся другими паттернами.
Rebbit, не запутывай и не давай запутать другим.
Нет никакого NULL на этапе проектирования. Есть объект "планета", которому при создании передается ссылка на "солнце". Всё!
А кто будет передавать эту ссылку? И самое главное - следить за корректностью передаваемых данных?
Нет никакого NULL на этапе проектирования. Есть объект "планета", которому при создании передается ссылка на "солнце". Всё!
Стоп-стоп-стоп... Я уже почти согласен с вашим решением, но(!) я все еще не могу понять вот что:
Пусть у меня не создался экземпляр Солнца - не важно по какой причине(не хватило памяти в системе, превышен лимит солнц, который был задан, абсолютно не важно), и так получилось что в мой конструктор передалось некоректное значение, к примеру NULL.
По совету Rebbit, я обработаю эту исключительную ситуацию - вставив в конструктор код
delete this;
Теперь меня интересует - а что дальше делать моей программе?? аварийно завершаться? Или такое проэктирование программы не дает мне механизмов обработки исключительных ситуаций??
Ссылку будет передавать тот код, который создает планету. Что тут не ясного?
Что значит "следить за корректностью"?
Как планета может определить корректность солнца? :)
Стоп-стоп-стоп... Я уже почти согласен с вашим решением, но(!) я все еще не могу понять вот что:
Пусть у меня не создался экземпляр Солнца - не важно по какой причине(не хватило памяти в системе, превышен лимит солнц, который был задан, абсолютно не важно), и так получилось что в мой конструктор передалось некоректное значение, к примеру NULL.
По совету Rebbit, я обработаю эту исключительную ситуацию - вставив в конструктор код
delete this;
Теперь меня интересует - а что дальше делать моей программе?? аварийно завершаться? Или такое проэктирование программы не дает мне механизмов обработки исключительных ситуаций??
А при чем тут проектирование и NULL?
Чем эта ситуация отличается от той, когда пользователь ввел некорректное значение с клавиатуры?
Чем эта ситуация отличается от той, когда ты обратился к синглтону, а он не смог создаться?
В такой пострановке (кидать исключение или проверить условие) эти вопросы не имеют отношения к проектированию, а сл-но и к паттернам проектирования. Это вопросы реализации.
Задача: Получать последовательно простые числа.
Для получения следующего просто числа N надо помнить все предыдущие простые числа до корень из N и пытаться делить на них. Ето сводится к тому что все уже найденные простые числа надо помнить.
Так как простые числа и в Африке простые нет смысла создавать несколько обектов для их поиска. Иначе уйдет больше памяти и каждому обекту придется все числа проверять по новой.
Похоже на синглетон :) но нет.
Припустим получать последовательность простых чисел надо в нескольких потоках. Напрашивается метод
long getNextPrimeNumber()
который будет возвращать ети числа попорядку. Синглетон уже не подходит так как каждый обект должен помнить номер следующего простого числа, которое надо отдать.
Конечно можно заставить помнить номер следующего простого числа метод который будет вызывать чтото типа
long getPrimeNumber(int indexPrimeNumber)
но такой подход мне кажется не совсем хорошим.
Сделал 2 класса
private class PrimeNumberGenerator {
..........
// тут хранение найденных чисел и поиск новых
..........
}
private static PrimeNumberGenerator generator = new PrimeNumberGenerator();
private int indexPrimeNumber;
public long getNextPrimeNumber() {..........}
.........................
}
Или всетаки не нужна ?
соответственно переменная indexPrimeNumber в каждом обєкте (потоке) будет хранить разное значение. Я бы например сделал класс PrimeNumber синглтоном, и сущность класса PrimeNumberGenerator передал в PrimeNumber.
Интересно будет услышать другие решения.
Если объект, в т.ч. и кеш, должен быть расшарен между несколькими потоками, еще не основание делать его синглтоном. Синглтон имеет другое назначение. Вы перепутали синглтон с объектом синхронизированным между несколькими потоками.
Обратитесь ещё раз к определению и назначению синглтона:
Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Для чего нам в данном случае гарантии? Чем ОПАСНО создание двух кешей? Снижением производительности? Это не опасно, да и легко обнаружимо и устранимо.
А тогда зачем лишать пользователя класса возможности создание нескольких кешей?
Зачем нужно несколько кешей? Ну у меня фантазия бурная, могу нагенерировать вариантов. :)
Например, хочу две независимые подсистемы. Настолько независимые, что и кеш у них будет разный. Для чего? Например, хочу сравнивать производительность двух алгоритмов, завязанных на генерацию простых чисел. Чтоб результаты сравнения были объективными, надо чтоб системы были независимы.
Или другой пример: мне "дешевле" иметь несколько небольших кешей, чем реализовывать синхронизацию доступа между несколькими потоками. Кстати, очень жизненная ситуация.
Так что синглтон здесь совсем не нужен.
Поясните как это может быть:
[quote=Википедия]
Гарантирует, что у класса есть только один экземпляр
[/quote]
[quote=Википедия]
допускает переменное число экземпляров;
[/quote]
Не понимаю второй фразы, как и нескольких других:
# допускает уточнение операций и представления;
# допускает переменное число экземпляров;
# большая гибкость, чем у операций класса.
Думаю, это "трудности перевода". :)
"допускает переменное число экземпляров" - думаю, надо читать, как "допускает определенное (фиксированное) число экземпляров".
Думаю, это "трудности перевода". :)
"допускает переменное число экземпляров" - думаю, надо читать, как "допускает определенное (фиксированное) число экземпляров".
так чему верить то?
по контексту больше склоняюсь к варианту "Гарантирует, что у класса есть только один экземпляр"
я понимаю что вариантов можно много придумать. суть то в том - что Rebbit поставил конкретную задачу - один такого рода кеш для всей системы - и аргументация производительности для него например очень важна.
не нужен только в том случае если алгоритм будет верно работать, но я пока не увидел других вариантов разрешения задачи.
Не вижу требования "один такого рода кеш для всей системы".
Не вижу смысла в этом требовании.
Не вижу смысла для удовлетворения этого требования применения синглтона. Если у тебя однооконный интерфейс, ты же не делаешь окно синглтоном. Оно просто одно и этого достаточно.
По поводу аргументации производительности.
Синглтон - это не шаблон решения задач производительности!
Я показал конкретный пример, когда синглтон будет резко снижать производительность, т.к. его придется шарить между потоками.
не нужен только в том случае если алгоритм будет верно работать, но я пока не увидел других вариантов разрешения задачи.
А с чего бы ему работать не верно не используя синглтон? :)
Вот тебе другой вариант (псевдокод):
main()
{
PrimeNumbersGenerator primeNumbersGenerator;
createThread(primeNumbersGenerator);
createThread(primeNumbersGenerator);
createThread(primeNumbersGenerator);
wait(threads);
}
Создается объект PrimeNumbersGenerator, передается (шарится) между порожденными тредами.
Всё!
Алгоритм будет работать верно? :D
Не обязательно, но поидее одним екземпляром в потоке можно обойтись.
Так и надо. В каждом потоке нужно получить последовательность с начала.
Не согласен. Здесь есть 2 задачи (по факту даже 3 потому что еще есть проверка на простоту любого числа переданого пользователем)
1. Поиск простых чисел (PrimeNumberGenerator)
2. Отдача чисел пользователю попорядку. Здесь же запрос на поиск новых если они еще не найдены. (PrimeNumber).
Не вижу смысла решать 2 задачи в одном классе да еще и в синглетоне. PrimeNumberGenerator делает свое дело и его задача в повышении производительности.
PrimeNumber - предоставляет интерфейс использования для PrimeNumberGenerator и контролирует его единственность.
Если Greenу захочется проверить производительность какихто алгоритмов или еще чего интересного в голову придет - ему не придется вносить изменения дальше чем в PrimeNumber.
Теперь у меня вопрос. Глобальные данные плохо. А приватные глобальгие поля тоже плохо ?
Ведь если придется чтото менять то изменения будут только на уровне класса где есть ето приватное поле и еще возможно в классе-типе етого поля.
Я понимаю что мой пример с простыми числами не совсем корректен так как простые числа можно считать константами но с ними я столкнулся в процессе роботы, а ничего лутше придумать не могу.
Вот тебе другой вариант (псевдокод):
main()
{
PrimeNumbersGenerator primeNumbersGenerator;
createThread(primeNumbersGenerator);
createThread(primeNumbersGenerator);
createThread(primeNumbersGenerator);
wait(threads);
}
Создается объект PrimeNumbersGenerator, передается (шарится) между порожденными тредами.
Всё!
Алгоритм будет работать верно? :D
согласен - ето уже конструктивный пример. но что если нет возможности передать потокам "primeNumbersGenerator" в таком виде как ты написал. Потому что потоки создаются всегда без параметров. В жизни не всегда есть возможность написать код как нам этого хочется. Как тогда?
Потоки создаются с параметрами
*nix:
void *(*start_routine)(void*), void *arg);
void *start_routine(void *arg);
win32:
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);
DWORD WINAPI ThreadProc(
__in LPVOID lpParameter
);
Кроме того существуют другие способы передать объект в поток.
Ты кажется начинаешь путаться и путать других.
Сначала ты советовал: "ужно било сделать синглтон - так как в разных потоках тебе буден нужен один объект".
Т.е. для расшарки объекта ежду потоками?
Далее ты говорил: "один такого рода кеш для всей системы - и аргументация производительности для него например очень важна".
Т.е. синглтон должен был оптимизировать по быстродействию?
Далее, ты утверждаешь: "не нужен только в том случае если алгоритм будет верно работать".
Т.е. синглтон нужен для корректности работы алгоритма генерации простых чисел?
А теперь ты говоришь: "что если нет возможности передать потокам "primeNumbersGenerator" в таком виде как ты написал".
Т.е. синглтон - это способ передачи объектов другим потокам?
Ты уж определись, для чего в данном случае понадобился синглтон?
Все вышеперечисленные "необходимости" на самом деле обходятся без синглтона, как я уже сказал и показал. Более того, синглтон не предназначен для решения этих проблем.
{
private:
Error();
public:
Error* GetInstance();
void ReturnError(int error) : m_error(error) { };
void DecypherError(string &errordesc);
private:
int m_error;
}
Теперь я имею гибкую систему возврата кодов ошибок. Или может все-таки есть более элегантное решение без синглтона? =)
Теперь я имею гибкую систему возврата кодов ошибок. Или может все-таки есть более элегантное решение без синглтона? =)
Эксепшены? )
А по-моему очень даже классно. Отличный способ узнать что где-то что-то упало.
Даже в обычной многопоточной программе каждый поток имеет свой LastError.
Это совсем не классно по двум причинам:
1. можна не обработать ексепшн(забыть например)
2. если даже его обработать и засунуть в сех-фрейм, то при возникновении ексепшена - будут дополнительные затраты процессорного времени.
И вообще, как мне кажется - генерировать исключения нужно только в ситуациях критичных для правильной работы программы.
2Green
А если без многопоточного приложения. Разве такой пример использования синглтона - не причисляет его к "добру" ?
А если без многопоточного приложения. Разве такой пример использования синглтона - не причисляет его к "добру" ?
Многопоточное приложение я привел только как пример того, что известный всем LastError не является синглтоном.
Объясни пожалуйста, для чего конкретно ты собираешься использовать синглтон в своем примере?
Как уже выяснилось, частая ошибка делать синглтоном класс только потому, что в программе требуется единственный экземпляр этого класса.
"Требуется только один" и "должно быть не более одного" - это разные вещи.
Почему же два объекта не нужно и бессмысленно?
Единственное из-за чего ты собираешься использовать синглтон, так это для глобализации данных.
А теперь идем к самым первым постам темы и смотрим:
Может, повезет и система не будет нуждаться в расширении в этом направлении, а может не повезти... :)
Не кажется, что уже ходим по кругу. И все эти "если..."
Тогда и оружие можно направлять на людей, ЕСЛИ оно никогда не будет заряжено.
И пьяным за руль садиться, ЕСЛИ никуда не поедешь.
И телефон с собой не брать, ЕСЛИ знаешь, что сегодня никто не позвонит.
И синглтон использовать, ЕСЛИ система не будет разрастаться.
А потом...
смотри здесь:
Т.к. вся система изначально могла в приложении существовать только в единственном экземпляре, то сама она и многие её подсистемы были сделаны синглтонами ("одиночками"), кроме того в системе существует множество глобальных объектов.
За время своего существования (около 10 лет) система обросла множеством таких подсистем и глобальных объектов. И вот в один прекрасный момент понадобилось создать несколько таких систем в одном приложении. Вот тогда и почувствовалось всё зло от глобальных данных и синглтонов.
Очень просто,- есть две независимые системы, которые генерят и обрабатывают эти ошибки. :)
Это уже в какую-то параною переростает - а если объектов будет больше чем один.
А не использовать void* - это параноя или просто хороший стиль?
Немного пофантазировав на эту тему - так можно и вообще перестать программировать - "А если в 2015 году придумают чтоб в программе было две функции main - и выполнялись они в отдельных тредах одновременно, и сделают это стандартом". Это конечно, шутка, но все-таки, я думаю, какие-то ограничения должны ж накладываться на программу, даже с учетом того, что она должна расширяться..
Ну вот и давай наложим ограничение: не использовать синглтоны. :)
Я же агитирую: "никогда ни за что на свете не используйте синглтоны!"
Я предупреждаю, что "черезмерное употребление может быть опасно для Вашего здоровья".
Поэтому все эти попытки найти идеальный вариант, где синглтон просто необходим, бессмыслены, т.к. без него можно обойтись, а уж использовать его или нет - твое дело.
Вот несколько лирических строк:
"Каждое типовое решение описывает некую повторяющуюся проблему и ключ к её разгадке, причём таким образом, что вы можете пользоваться этим ключом многократно, ни разу не придя к одному и тому же результату. Мне кажется, что типовые решения можно сравнить с полуфабрикатом: чтобы получить удовольствие от еды, вам придётся довершить приготовление блюда по собственному рецепту." - М. Фаулер, "Архитектура корпоративных программных приложений"
Моё мнение: Типовые решения могут обернуться как злом, так и добром, взависимости от умения их эффективно использовать.
Но такое уже сейчас реализовано :)
Домены приложений в .net тому пример. Нужна вторая копия системы - пожалуйста, создаем еще один AppDomain с точкой входа Program.Main и радуемся жизни.
Домены приложений в .net тому пример. Нужна вторая копия системы - пожалуйста, создаем еще один AppDomain с точкой входа Program.Main и радуемся жизни.
Это все заговор.. Сплошной заговор, против тех, кто пользуется синглтонами.. :D
Помоему слово hardcase прозвучало в защиту синглетона.
На сколько я знаю в каждом AppDomain будет свой обект-тип для синглетона. Соответственно и инстансов тоже будет по одному на каждый AppDomain. Правда не знаю как между этими AppDomain взаимодействие организировать но такая возможность есть.
ЗЫ. Также слышал от знакомого шарписта что можно сделать синглетон на уровне потока, но не проверял так ли это.
ЗЫЫ. rSolanovу респект за оживление дискусии. Ато мне тоже чтото скучновато стало.
Встроенными средствами .net можно через .NET Remoting, как альтернатива - искать управляемую обертку над каналами (pipes).
Можно. Навешиваем на статическое поле атрибут ThreadStaticAttribute и у нас для каждой нити свое поле.
если гвози забиваешь - добро, а если по пальцу е...нешь - зло
Очень внимательно прочитал данную дискуссию, пока только увидел только минусы использования одиночки. Сам в .Net - для хранения глобальных данных использовал что вроде
{
.......................................
}
class ApplicationGlobal
{
private static UserDataProvider m_userDataProvider;
public static UserDataProvider UserDataProvider
{
get
{
if(m_userDataProvider == null)
m_userDataProvider = new UserDataProvider();
return m_userDataProvider;
}
}
}