Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > так ли плох синглтон


Автор: GoldFinch 5.8.2009, 10:15
в английской википедии, в статье про синглтон ( http://en.wikipedia.org/wiki/Singleton_pattern )
есть пяток ссылок ([1]-[6], http://en.wikipedia.org/wiki/Singleton_pattern#cite_note-0) на статьи в которых говорится что синглтон это антипаттерн

так например пишут что синглон это не ООП, а замена глобальных переменных на глобальные функции,
что синглтон реализует "i know where you lives" антипаттерн, и повышает число скрытых зависимостей

/discuss

Автор: Lazin 5.8.2009, 10:23
В общем случае наверное да, но есть ситуации, в которых это не так.
Я для себя выработал такое правило: объект может быть глобальным, только если его состояние не изменяется или для приложения не важно его состояние, например логгер и стандартные потоки ввода вывода, у них есть состояние, но как правило, приложению фиолетово, что выводили в лог или в поток минуту назад. 
Сам подумай, передавать в каждый объект указатель на std::cout или %loggername% - глупо.

Автор: azesmcar 5.8.2009, 10:27
Ссылки на высказывания людей с сомнительным авторитетом. Андрей Александреску около 30-и страниц этому шаблону проектирования, а Алексу Миллеру (кто это такой вообще?) вдруг взбрело в голову что это зло. Бегу удалять синглтоны smile 
Разумеется не надо строить свой код на одних синглтонах, но есть ситуации когда без него никак. Тот же логер, настройки и многое другое.

Автор: GoldFinch 5.8.2009, 10:29
Lazin, std::cout  - не синглтон. это обычная глобальная переменная. и это часть стандартной библиотеки
у std::cout есть состояние, оно изменяется манипуляторами и флагами.

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

Автор: mes 5.8.2009, 10:30
В принципе в приложении должен быть только один синглетон - приложение.  smile 
А вот решение проблемы синглетонами - зло.

Добавлено @ 10:32
Цитата(GoldFinch @  5.8.2009,  09:29 Найти цитируемый пост)
но допустим, логгер может быть синглтоном, а все остальное - не может. значит синглтон плох, а логгер - это исключение из правила? 

нет не исключение, а быстрое решение проблемы, без существенных недостатков, т.е маленькое такое почти безвредное зло  smile 

Автор: Любитель 5.8.2009, 10:35
Цитата(mes @  5.8.2009,  10:30 Найти цитируемый пост)
В принципе в приложении должен быть только один синглетон - приложение. 

В идеале - да. smile 

Автор: Acer 5.8.2009, 10:59
Цитата(Lazin @ 5.8.2009,  09:23)
В общем случае наверное да, но есть ситуации, в которых это не так.
Я для себя выработал такое правило: объект может быть глобальным, только если его состояние не изменяется или для приложения не важно его состояние, например логгер и стандартные потоки ввода вывода, у них есть состояние, но как правило, приложению фиолетово, что выводили в лог или в поток минуту назад. 
Сам подумай, передавать в каждый объект указатель на std::cout или %loggername% - глупо.

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

Автор: Lazin 5.8.2009, 11:27
Цитата(GoldFinch @  5.8.2009,  10:29 Найти цитируемый пост)
Lazin, std::cout  - не синглтон. это обычная глобальная переменная. и это часть стандартной библиотеки
cout можно рассматривать как singletone, вообще, я имел ввиду стандартные потоки ввода вывода в общем

Цитата(GoldFinch @  5.8.2009,  10:29 Найти цитируемый пост)
у std::cout есть состояние, оно изменяется манипуляторами и флагами.

да, но если не пользоваться манипуляторами(или правильно ими пользоваться), то иногда можно считать что его нет smile 

Автор: Earnest 5.8.2009, 12:23
Что за странная постановка вопроса? Даже GOTO не является безусловным злом. "Все есть яд и все есть лекарство". Просто голову нужно применять почаще.
Насчет приложения. Допустим, у меня есть куча объектов типа логгер (т.е. ровно один инстанс на сесссию). Какой-нибудь менеджер событий, очередь чего-нибудь глобальная, регистраторы разные, да мало ли. И что, по вашей логике, я их все из объекта-пртложения должна получать? И во что тогда объект-приложение превратиться? При том, что информация о как минимум половине этих объектов ему нафиг не нужна для нормальной жизни. Наоборот, преимущество синглетона в том, что он ни от кого не зависит и гарантирует единственность. Более того, семантика синглетона прекрасно расширяется на более сложные случаи, например, один экземпляр в потоке, или один экземпляр в документе, причем ни поток ни документ совершенно не должны знать об этих конкретных объектах. Т.е. это прекрасное средство для устранения лишних зависимостей.

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

Автор: azesmcar 5.8.2009, 12:31
Earnest

Полностью согласен. 
Злом считаю не синглтон, а антипатерн "Золотой молот", когда человек изучив синглтон пытается его внедрить во все мыслимые и немыслимые щели.

Автор: GoldFinch 5.8.2009, 12:33
Цитата(Earnest @  5.8.2009,  13:23 Найти цитируемый пост)
Т.е. это прекрасное средство для устранения лишних зависимостей.

зависимости никуда не деваются.
они просто становятся неявными, убираются из интерфейса в реализацию

если объекту не надо с чем-то взаимодействовать - то он не должен об этом знать, а если надо - это должно быть написано в интерфейсе объекта

Добавлено через 13 минут и 52 секунды
Цитата(Earnest @  5.8.2009,  13:23 Найти цитируемый пост)
Какой-нибудь менеджер событий, очередь чего-нибудь глобальная, регистраторы разные, да мало ли. И что, по вашей логике, я их все из объекта-пртложения должна получать? 

по моей логике должно быть
queue = new SomeQueue();
eventMan = new EventManager(queue);

если логгер не имеет состояния, то он вобщем-то и не объект, 
(у объекта есть внутреннее состояние, которое изменяется вызовом его методов)
и то как организован доступ к этому логгеру - через глобальную переменную, глобальную функцию или еще как - не важно.
для "объекта" без состояния нет разницы между
Logger log; ... log.write(...);
или
Logger::Instance().write(...);
или
Logger_write(...);

Автор: mes 5.8.2009, 12:49
Цитата(Earnest @  5.8.2009,  11:23 Найти цитируемый пост)
Более того, семантика синглетона прекрасно расширяется на более сложные случаи, например, один экземпляр в потоке, или один экземпляр в документе, 


Earnest, в понятие паттерна синглетон входит случай лишь когда класс объекта,  сам регулирует единичное кол-во своих инстанций 
или когда некто внешний регулирует кол-во инстанций равным в нашем случае единице ?

Автор: Earnest 6.8.2009, 06:53
mes, я и не имею в виду, что описанные расширения продолжают быть синглетоном. И потом, каноническое описание паттернов - это что евангелие новое? Смысл паттернов в том, чтобы дать пример логической организации кода и побудить к творчеству. smile 
Цитата(GoldFinch @  5.8.2009,  13:33 Найти цитируемый пост)
по моей логике должно быть
queue = new SomeQueue();

А где гарантия того, что каждый второй клиент не начнет создавать свои очереди? Синглетон же гарантирует единственность. А что касается зависимостей - ты не прав. Нет там никакой зависимости на уровне реализации, во всяком случае можно так сделать. И вот это высказывание
Цитата(GoldFinch @  5.8.2009,  13:33 Найти цитируемый пост)
если объекту не надо с чем-то взаимодействовать - то он не должен об этом знать, а если надо - это должно быть написано в интерфейсе объекта

более чем спорно. Для одного интерфейса может быть несколько реализаций: одни с зависимостями, другие без...
Кроме того, зависимости можно приблизительно учитывать как "прямого видение" определения класса \интерфейса \ функции. Так что когда зависимости только в реализации - их безусловно меньше.

Автор: Любитель 6.8.2009, 07:10
Ну вот взять тот же логгер. Допустим у нас есть реальный Logger, с записью куда-нибудь и отсылкой нотификейшенов на e-mail и есть FakeLogger - для тестов. Работаем с логгером мы только через ILogger. Синглетон в чистом виде - явно не пойдёт. Нужна какая-то фабрика логгеров. Она же управляет временем жизни. И вообще созданием подобных объектов должны управлять DI-фреймворки smile Синглетон - это костыль. Но зачастую приемлемый. Всему своё место и место каждой вещи под солнцем smile

Автор: Lazin 6.8.2009, 07:37
ох уж эти enterprise программисты smile 

Автор: azesmcar 6.8.2009, 08:23
Цитата(Любитель @  6.8.2009,  07:10 Найти цитируемый пост)
Ну вот взять тот же логгер. Допустим у нас есть реальный Logger, с записью куда-нибудь и отсылкой нотификейшенов на e-mail и есть FakeLogger - для тестов. Работаем с логгером мы только через ILogger. Синглетон в чистом виде - явно не пойдёт. Нужна какая-то фабрика логгеров. Она же управляет временем жизни. И вообще созданием подобных объектов должны управлять DI-фреймворки smile Синглетон - это костыль. Но зачастую приемлемый. Всему своё место и место каждой вещи под солнцем smile 

По моему тут больше подойдет препроцессор #ifdef _DEBUG. Зачем нагружать программу фабрикой логеров для тестирования?

Автор: chaos 6.8.2009, 08:32
Цитата(azesmcar @ 6.8.2009,  05:23)
Цитата(Любитель @  6.8.2009,  07:10 Найти цитируемый пост)
Ну вот взять тот же логгер. Допустим у нас есть реальный Logger, с записью куда-нибудь и отсылкой нотификейшенов на e-mail и есть FakeLogger - для тестов. Работаем с логгером мы только через ILogger. Синглетон в чистом виде - явно не пойдёт. Нужна какая-то фабрика логгеров. Она же управляет временем жизни. И вообще созданием подобных объектов должны управлять DI-фреймворки smile Синглетон - это костыль. Но зачастую приемлемый. Всему своё место и место каждой вещи под солнцем smile 

По моему тут больше подойдет препроцессор #ifdef _DEBUG. Зачем нагружать программу фабрикой логеров для тестирования?

как вариант логгер который пишет plain text, логгер который пишит в xml формате или логгер который шлет все на мыло или еще куда

Добавлено через 4 минуты и 40 секунд
Цитата(Любитель @ 6.8.2009,  04:10)
... DI-фреймворки ...

офтоп небольшой smile 
PocoCapsule например, больше не встречал готовых реализаций DI\IoC

Автор: azesmcar 6.8.2009, 08:39
Цитата(chaos @  6.8.2009,  08:32 Найти цитируемый пост)
как вариант логгер который пишет plain text, логгер который пишит в xml формате или логгер который шлет все на мыло или еще куда 

Такое я бы реализовал передав ему аргумент writer какой нибудь, создать единый интерфейс для logwriter -а, наследовать от него xmlWriter, mailWriter и plainWriter например...а дальше так
Код

Logger::Instance()->write(plainWriter(), "plain text");
Logger::Instance()->write(xmlWriter(), "<xml></xml>");
...

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

Автор: Любитель 6.8.2009, 09:31
Цитата(azesmcar @  6.8.2009,  08:23 Найти цитируемый пост)
По моему тут больше подойдет препроцессор #ifdef _DEBUG. Зачем нагружать программу фабрикой логеров для тестирования? 

Почему я не имею права запускать юнит-тесты на релизе?! Ещё и пересобирать.. Не, не хочу smile

Цитата(chaos @  6.8.2009,  08:32 Найти цитируемый пост)
PocoCapsule например, больше не встречал готовых реализаций DI\IoC 

Вопрос, как таковой, не имеет отношения к С++ (точнее так - отношениее имеет, не имеет прямой привязки), так что...

Цитата(azesmcar @  6.8.2009,  08:39 Найти цитируемый пост)
Такое я бы реализовал передав ему аргумент writer какой нибудь, создать единый интерфейс для logwriter -а, наследовать от него xmlWriter, mailWriter и plainWriter например...а дальше так

1. В твоём примере полиморфизм не сработает - ты передаёшь объект по ссылке.
2. У тебя просто writer выполняет то, что я называл логгером. smile Просто лишний уровень абстракции, но проблему создания объектов (сейчас ты их создаёшь заново при каждом вызове) это не решает.

Цитата(Lazin @  6.8.2009,  07:37 Найти цитируемый пост)
ох уж эти enterprise программисты

Да-да, мы такие smile Lazin, прости, но "с волками жить - по волчьи выть", как говорится smile 

Автор: azesmcar 6.8.2009, 09:40
Цитата(Любитель @  6.8.2009,  09:31 Найти цитируемый пост)
Почему я не имею права запускать юнит-тесты на релизе?! Ещё и пересобирать.. Не, не хочу smile

Я думал речь идет о простом тестировании, но название препроцессора всегда можно изменить smile 
#ifdef UNIT_TEST или как там? Компилятор что-то там определяет точно.

Цитата(Любитель @  6.8.2009,  09:31 Найти цитируемый пост)

1. В твоём примере полиморфизм не сработает - ты передаёшь объект по ссылке.

Одна поправка, константную ссылку, но что это меняет? с чего ему не работать? Полиморфизм работает как по указателю так и по ссылке. Правда я на скорую руку написал, просто идею показать..тут больше бы подошел статический полиморфизм на мой взгляд.

Код

Logger<XMLWriter>::Instance()->write(...);
Logger<PlainWriter>::Instance()->write(...);

так например.

Цитата(Любитель @  6.8.2009,  09:31 Найти цитируемый пост)
2. У тебя просто writer выполняет то, что я называл логгером. smile Просто лишний уровень абстракции, но проблему создания объектов (сейчас ты их создаёшь заново при каждом вызове) это не решает.

Ну это я для примера написал, разумеется это не идеальный код, я саму идею имел ввиду. А кстати фабрика логеров это тоже лишний уровень абстракции.

Автор: GoldFinch 6.8.2009, 10:57
azesmcar, статический полиморфизм  - он статический, а надо, чтобы в ран-тайме можно было дать объекту логгер, обычный, продвинутый, или Null-логгер.
фабрика - это не уровень абстракции, а вспомогательный объект\класс, не влияющий на абстракцию того что она производит
объекты же не знают как и произвели  - просто создали, создали на фабрике или клонировали

Добавлено @ 10:59
azesmcar, а юнит тест это
Код

 unitTest()
{
  Unit u=new Unit();
  u.do_some();
}

Автор: azesmcar 6.8.2009, 11:02
Цитата(GoldFinch @  6.8.2009,  10:57 Найти цитируемый пост)
azesmcar, статический полиморфизм  - он статический, а надо, чтобы в ран-тайме можно было дать объекту логгер, обычный, продвинутый, или Null-логгер.

Кто это так решил что надо? Я писал как раз о том, чтобы сделать это во время компиляции а не в рантайме, потому как не увидел смысла оставлять это на рантайм.

Цитата(GoldFinch @  6.8.2009,  10:57 Найти цитируемый пост)
фабрика - это не уровень абстракции, а вспомогательный объект\класс, не влияющий на абстракцию того что она производит
объекты же не знают как и произвели  - просто создали, создали на фабрике или клонировали

Да, но уровень абстракции нужен для работы фабрики.

Цитата(GoldFinch @  6.8.2009,  10:57 Найти цитируемый пост)
azesmcar, а юнит тест это

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

Автор: mes 6.8.2009, 11:08
Цитата(Earnest @  6.8.2009,  05:53 Найти цитируемый пост)
я и не имею в виду, что описанные расширения продолжают быть синглетоном. 

По контексту обсуждаемого поста это не следовало. smile
Цитата(Earnest @  6.8.2009,  05:53 Найти цитируемый пост)
И потом, каноническое описание паттернов - это что евангелие новое? 

Конечно нет, но логично предположить, что обсуждение синглетона в данной теме, исходя из поста тс, ведется именно в рамках его канонического описания smile
Цитата(Earnest @  6.8.2009,  05:53 Найти цитируемый пост)
Смысл паттернов в том, чтобы дать пример логической организации кода и побудить к творчеству. smile 

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

Автор: GoldFinch 6.8.2009, 12:43
Цитата(azesmcar @  6.8.2009,  10:40 Найти цитируемый пост)
Logger<XMLWriter>::Instance()->write(...);

вот тут полиморфизма вообще нет.

Автор: azesmcar 6.8.2009, 12:48
Цитата(GoldFinch @  6.8.2009,  12:43 Найти цитируемый пост)
вот тут полиморфизма вообще нет. 

это тебе так кажется. Это статический полиморфизм.

Автор: GoldFinch 6.8.2009, 12:49
fixed.

Добавлено через 33 секунды
Цитата(GoldFinch @  6.8.2009,  13:43 Найти цитируемый пост)
вот тут полиморфизма вообще нет.

было бы 
typedef ... logger_t;
...
logger_t::Instance()->write(...);

был бы полиморфизм.

хотя совсем не понятно, почему не просто 
logger_t::write(...);
(то что используется вызов метода по указателю, ->write(...); могло бы использоваться для виртуального метода, но это же не тот случай)

Добавлено через 2 минуты и 59 секунд
какбэ запись
  Logger<XMLWriter>
совершенно эквивалентна
 Logger_XMLWriter

а вот
  template<typename W> Logger<W> 
это совсем другое

Автор: azesmcar 6.8.2009, 12:53
Цитата(GoldFinch @  6.8.2009,  12:49 Найти цитируемый пост)

хотя совсем не понятно, почему не просто 
logger_t::write(...);

что такое logger_t??? Куда он пишет и как дать понять куда я хочу писать? В XML Или в Plain text?

Цитата(GoldFinch @  6.8.2009,  12:49 Найти цитируемый пост)
(то что используется вызов метода по указателю, ->write(...); могло бы использоваться для виртуального метода, но это же не тот случай) 

Каким образом вызов метода по указателю связан с виртуальным методом?
Код

#include <iostream>

struct Base
{
    virtual void f() const = 0;
};

struct A : Base
{
    virtual void f() const {
        std::cout << "A::f()" << std::endl;
    }
};

struct B : Base
{
    virtual void f() const {
        std::cout << "B::f()" << std::endl;
    }
};

void foo(const Base& r)
{
    return r.f(); //вызов виртуального метода через ТОЧКУ
}

int main()
{
   foo(A());
   foo(B());
}

Но я как раз хочу сказать что динамический полиморфизм тут совершенно не нужен (ну или я не понял зачем он нужен, может у автора есть причина). 

Автор: mes 6.8.2009, 13:02
Цитата(azesmcar @  6.8.2009,  11:53 Найти цитируемый пост)
что такое logger_t??? Куда он пишет и как дать понять куда я хочу писать? В XML Или в Plain text?

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

Добавлено @ 13:04
Ну а вообще мне кажется, что в Вашем споре между  GoldFinch и  azesmcar, каждый спорит о своем..
Неплохо бы прояснить в чем суть то спора smile

Автор: GoldFinch 6.8.2009, 13:11
ну вообще тема про синглтоны

Автор: azesmcar 6.8.2009, 13:13
Цитата(GoldFinch @  6.8.2009,  12:49 Найти цитируемый пост)
какбэ запись
  Logger<XMLWriter>
совершенно эквивалентна
 Logger_XMLWriter

а вот
  template<typename W> Logger<W> 
это совсем другое

я не понял чем мой вариант отличался от "совсем другого"?

Цитата(mes @  6.8.2009,  13:02 Найти цитируемый пост)
Ну а вообще мне кажется, что в Вашем споре между  GoldFinch и  azesmcar, каждый спорит о своем..
Неплохо бы прояснить виобще в чем суть то спора smile

Я и сам не знаю smile

Автор: GoldFinch 6.8.2009, 13:22
azesmcar, а зачем объекту надо решать, кто куда ему будет писать? это уже много обязанностей, когда объект и основную работу выполняет и решает куда ему лог писать.
другое дело, что объекту нужен логгер для логов - ему дают логгер
(не каждый объект должен вести логи)
а вот нужно вести лог вообще, или в какой файл или не файл этот лог должен вестись - решает тот кто объект создает.
если лог не нужен, объекту дают null-логгер который ничего не делает, если нужно писать в файл или в сокет дают соответствующий логгер.

а статически или динамически - другой вопрос, будет это
    class Foo {
       Foo(ILogger* log) 
       ... 
    };
или
    template<typename LOG>
    class Foo { 
        Foo(LOG& log) 
        ...
    };
или
    template<typename CFG>
    class Foo { 
        Foo(CFG::logger_t log) 
        ...
    };

Добавлено через 1 минуту и 38 секунд
Цитата(azesmcar @  6.8.2009,  14:13 Найти цитируемый пост)
чем мой вариант отличался от "совсем другого"?

там что класс Writer жестко задан, и его никак не поменять

Автор: azesmcar 6.8.2009, 13:28
Цитата(GoldFinch @  6.8.2009,  13:22 Найти цитируемый пост)
там что класс Writer жестко задан, и его никак не поменять 

template<typename W> Logger<W>
Как ты будешь использовать этот класс?
Код

Logger<Writer>::Instance()->write(...);

не так что ли?

Автор: GoldFinch 6.8.2009, 13:36
azesmcar, я имел ввиду
  template<typename W>
  ...
  {
     Logger<W>::write("text");
  }

и

  ...
  {
     Logger<ConcreteWriter>::write("text");
  }

это ведь разные вещи?

как например
  AbstractFoo* foo;
  foo->write("text");
и 
  ConcreteFoo* foo;
  foo->write("text");

Автор: azesmcar 6.8.2009, 13:43
Цитата(GoldFinch @  6.8.2009,  13:36 Найти цитируемый пост)
azesmcar, я имел ввиду
  template<typename W>
  ...
  {
     Logger<W>::write("text");
  }

и

  ...
  {
     Logger<ConcreteWriter>::write("text");
  }

это ведь разные вещи?

как например
  AbstractFoo* foo;
  foo->write("text");
и 
  ConcreteFoo* foo;
  foo->write("text");

Где-то его все равно надо будет конкретизировать. Я показал именно конкретизированную часть, хочешь передавай конркетный врайтер в функции main, какая разница. Это решается в зависимости от требований.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)