подскажите паттерн №2
От: Graf Alex Украина http://grafalex.oberon.kiev.ua
Дата: 14.06.07 10:29
Оценка:
Пару вопросов по паттернам...

вопрос 1: отвлеченный от дела... чисто для научного интереса...
Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?

Вопрос 2:
Пишу библиотеку. В ней есть ModuleA, ModuleB, ModuleC. Для каждого модуля объявлен интерфейс, который в принципе укладывается в одну функцию типа SomeResultType runModuleA(<parameters>); Хотя как интерфейс с одним виртуальным методом тоже возможен.
Реализации модулей понятно друг о друге ничего не знают и общаются только через интерфейсы.

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

Проще говоря пускай ModuleA и ModuleB вызывают ModuleC в своей реализации.
Хотелось код вызова писать так:
ModuleAResult aRes = runModuleA();
ModuleBResult bRes = runModuleB(aRes);

а не так:
ModuleAResult aRes = runModuleA(getCurrentModuleCRunningFunction());
ModuleBResult bRes = runModuleB(aRes, getCurrentModuleCRunningFunction());

Т.е. по сути пользователь должен как то регистрировать свой модуль.

Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...


22.06.07 12:16: Перенесено модератором из 'C/C++. Прикладные вопросы' — Хитрик Денис
Re: подскажите паттерн №2
От: Glоbus Украина  
Дата: 14.06.07 13:32
Оценка:
Здравствуйте, Graf Alex, Вы писали:

GA>Пару вопросов по паттернам...


GA>вопрос 1: отвлеченный от дела... чисто для научного интереса...

GA>Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?

А при чем тут синглетон и доступность всей программе? Синглетон — это возможность создавать ограниченное (обычно 1) количество объектов одного класса. Тебе что нужно — чтобы все классы видели твой объект или чтобы экземпляр был 1?


GA>Вопрос 2:

GA>Пишу библиотеку. В ней есть ModuleA, ModuleB, ModuleC. Для каждого модуля объявлен интерфейс, который в принципе укладывается в одну функцию типа SomeResultType runModuleA(<parameters>); Хотя как интерфейс с одним виртуальным методом тоже возможен.
GA>Реализации модулей понятно друг о друге ничего не знают и общаются только через интерфейсы.

GA>Хотелось бы предоставить пользователю моей библиотеки иметь возможность заменять мои модули своими, но при этом если пользователь ничего не заменял используются реализации из моей библиотеки.


GA>Проще говоря пускай ModuleA и ModuleB вызывают ModuleC в своей реализации.

GA>Хотелось код вызова писать так:
GA>
GA>ModuleAResult aRes = runModuleA();
GA>ModuleBResult bRes = runModuleB(aRes);
GA>

GA>а не так:
GA>
GA>ModuleAResult aRes = runModuleA(getCurrentModuleCRunningFunction());
GA>ModuleBResult bRes = runModuleB(aRes, getCurrentModuleCRunningFunction());
GA>

GA>Т.е. по сути пользователь должен как то регистрировать свой модуль.


А как твоя софтина без регистрации узнает, что есть еще какие-то модули? Посмотри на тот же СОМ — приходжится регистрится, чтобы твои интерфейсы и реализации были доступны.

GA>Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...


Трудно тебе что-либо посоветовать, потому как задача ну очень вобщем поставлена.
З.Ы. Вообще для того, чтобы можно было вот так вот прозрачно подменять реализации без регистрации придется укладывать твои модули в некоторое Прокрустово ложэ — например сказать, что есть папка, в эту папку пользователь должен положить свою длл-ну, при инициализации софтина сканит папку, грузит длл-ну и получает указатель на реализацию пользователя. Ну и соотвественно интерфейс этих длл-ин должен быть унифицирован. Короче говоря, тот же СОМ, только руками.
Удачи тебе, браток!
Re[2]: подскажите паттерн №2
От: Graf Alex Украина http://grafalex.oberon.kiev.ua
Дата: 14.06.07 14:00
Оценка:
Здравствуйте, Glоbus, Вы писали:

G>Здравствуйте, Graf Alex, Вы писали:


GA>>Пару вопросов по паттернам...


GA>>вопрос 1: отвлеченный от дела... чисто для научного интереса...

GA>>Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?
G>А при чем тут синглетон и доступность всей программе? Синглетон — это возможность создавать ограниченное (обычно 1) количество объектов одного класса. Тебе что нужно — чтобы все классы видели твой объект или чтобы экземпляр был 1?
Вот и меня это же смущает...
Обычно я пользуюсь синглтоном как единственным объектом... Мне в общем то пофиг что он доступен везде...
Просто хотел услышать мнение окружающих...

GA>>Вопрос 2:

GA>>Пишу библиотеку. В ней есть ModuleA, ModuleB, ModuleC. Для каждого модуля объявлен интерфейс, который в принципе укладывается в одну функцию типа SomeResultType runModuleA(<parameters>); Хотя как интерфейс с одним виртуальным методом тоже возможен.
GA>>Реализации модулей понятно друг о друге ничего не знают и общаются только через интерфейсы.

GA>>Хотелось бы предоставить пользователю моей библиотеки иметь возможность заменять мои модули своими, но при этом если пользователь ничего не заменял используются реализации из моей библиотеки.


G>А как твоя софтина без регистрации узнает, что есть еще какие-то модули? Посмотри на тот же СОМ — приходжится регистрится, чтобы твои интерфейсы и реализации были доступны.

Нет, понятное дело, что модули как то надо регистрировать...
GA>>Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...
G>Трудно тебе что-либо посоветовать, потому как задача ну очень вобщем поставлена.

Короче говоря, какие есть механизмы, которые позволят юзеру потставлять вместо дефолтных модулей свои?
Re: подскажите паттерн №2
От: dotidot Россия  
Дата: 14.06.07 14:44
Оценка:
Здравствуйте, Graf Alex, Вы писали:

GA>Пару вопросов по паттернам...

спроси гугл на тему "IOC контейнеры" — как раз то что описано, даже еще более обобщенно. Но в С++ я такого не видел.
Re[2]: подскажите паттерн №2
От: dotidot Россия  
Дата: 14.06.07 14:46
Оценка:
Здравствуйте, dotidot, Вы писали:

D>спроси гугл на тему "IOC контейнеры" — как раз то что описано, даже еще более обобщенно. Но в С++ я такого не видел.

ну или более приземлено: "plugin architecture"
Re[3]: подскажите паттерн №2
От: Аноним  
Дата: 15.06.07 07:09
Оценка:
GA>Короче говоря, какие есть механизмы, которые позволят юзеру потставлять вместо дефолтных модулей свои?

Ну первое что приходит на ум — реализуйте у себя COM
Re[3]: подскажите паттерн №2
От: Аноним  
Дата: 19.06.07 16:33
Оценка: +1
Здравствуйте, Graf Alex, Вы писали:

GA>Обычно я пользуюсь синглтоном как единственным объектом... Мне в общем то пофиг что он доступен везде...

GA>Просто хотел услышать мнение окружающих...

Модуль должен на вход получать внешние объекты (через ссылки/указатели/интерфейсы — не суть). О том, что провайдером данного объекта является синглтон, модулю знать не надо, иначе будут траблы при юнит-тестировании или рефакторинге. А так — подсунул модулю на вход какой-то другой инстанс (не от синглтона, а тот же мок-объект, например) — и все. Никаких редактирований кода, зависимостей и т.п.
Re[3]: подскажите паттерн №2
От: Aviator  
Дата: 20.06.07 11:19
Оценка:
Здравствуйте, dotidot, Вы писали:

D>Здравствуйте, dotidot, Вы писали:


D>>спроси гугл на тему "IOC контейнеры" — как раз то что описано, даже еще более обобщенно. Но в С++ я такого не видел.

D>ну или более приземлено: "plugin architecture"
IoC это автоматичиское разруливание зависимостей между доменными объектами, а то что автору нужно скорее плагинная архитектура.

Второй вопрос распадается на два:
Как сделать архитектурно?

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

Как реализовать технически?
Можно сделать через COM, то есть в точке входа прописывается guid или progId ком объекта, реализующего заданный интерфейс. Можно обойтись без кома — в каждой дллке будет функция ICustomMidule* Load(), возвращающая интерфейсный указатель. Если дллки динамически линкуются с CRT, то можно смело при необходимости использовать такой код:
   ICustomModule* someModule = Load();
   someModule->makeSomeWork();
   delete someModule;

Соответсвенно, всяки shared_ptr<> вполне актуальны. Ограничение — плагины и библиототека должны быть скомпилированы одним и тем же компилятором. При статической линковке надо кроме Load делать ещё и Free в дллке, то есть что-то типа
Free(someModule);

Ещё как вариант — возвращать не интефрейс, а handle объекта из дллки и, соответсвенно, использовать голый С. Это позволит получить более переносимое решение. Пример кода:
     typedef void* PLUGIN_HANDLE;

     PLUGIN_HANDLE hPluginInstance = Load();
     MakeSomeWork(hPluginInstance);
     Free(hPluginInstance);
Re: подскажите паттерн №2
От: NikeByNike Россия  
Дата: 20.06.07 16:30
Оценка:
Здравствуйте, Graf Alex, Вы писали:

GA>Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...


Паттерны — фабрики или билдер?
Нужно разобрать угил.
Re[4]: подскажите паттерн №2
От: rsn81 Россия http://rsn81.wordpress.com
Дата: 22.06.07 08:58
Оценка:
Здравствуйте, Aviator, Вы писали:

A> Как сделать архитектурно?

A> Как реализовать технически?
Наверно стоит посмотреть стандарт OSGI.
... << RSDN@Home 1.2.0 alpha rev. 679>>
Re: подскажите паттерн №2
От: Пётр Седов Россия  
Дата: 22.06.07 09:51
Оценка: -1
Здравствуйте, Graf Alex, Вы писали:
GA>Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?
Пуристы, наверное. Они любят догмы.
На самом деле singleton – вполне нормальное решение. Несколько примеров:
* Windows-реестр. Это глобальный объект в рамках компьютера. Доступен всем. Используется, к примеру, для регистрации COM-компонентов.
* Heap (распределитель памяти). Это глобальный объект в рамках процесса. Доступен всем (malloc/free, new/delete). Кстати, помимо C/C++ heap-а, имеется глобальный process heap от Windows (WinAPI-шная функция GetProcessHeap возвращает handle этого heap-а).
* Очередь оконных сообщений. Это глобальный объект в рамках потока (thread). Доступна всем. Кстати, вначале поток не имеет очереди. Windows создаёт очередь неявно, когда поток первый раз вызывает user-функцию (то есть WinAPI-шную функцию из user32.dll).
Пётр Седов (ушёл с RSDN)
Re[2]: подскажите паттерн №2
От: Пётр Седов Россия  
Дата: 25.06.07 23:14
Оценка:
Здравствуйте, Пётр Седов, Вы писали:

ПС>* Heap (распределитель памяти). Это глобальный объект в рамках процесса. Доступен всем (malloc/free, new/delete).

Я ошибся.
В рамках процесса может быть несколько C/C++ heap-ов:

Здесь App.exe использует статически link-уемый C/C++ run-time. При этом App.exe динамически link-уется с Some.dll, который в свою очередь динамически link-уется с msvcrt.dll. В результате в рамках процесса одновременно живут два C/C++ heap-а (зелёные прямоугольники).

ПС>Кстати, помимо C/C++ heap-а, имеется глобальный process heap от Windows (WinAPI-шная функция GetProcessHeap возвращает handle этого heap-а).

Тут всё правильно – Windows process heap действительно один на весь процесс (видимо, поэтому он и называется «process heap» ).
Пётр Седов (ушёл с RSDN)
Re[2]: подскажите паттерн №2
От: AndrewJD США  
Дата: 26.06.07 07:31
Оценка:
Здравствуйте, Пётр Седов, Вы писали:

ПС>Пуристы, наверное. Они любят догмы.

Скорее практики

ПС>На самом деле singleton – вполне нормальное решение. Несколько примеров:

ПС>* Windows-реестр. Это глобальный объект в рамках компьютера. Доступен всем. Используется, к примеру, для регистрации COM-компонентов.
Опять же все зависит от точки зрения. Действительно можно сказать, что реестр он глобальный в рамках компьютера и напрямую вызывать API. Но можно скзать что это конфигурация и где она хранится не имеет значения. Что мешает иметь несколько конфигураций одновременно?
Простой пример из жизни. Есть софтина которая позволяет работать с несколькими торговыми системами одновременно. Т.е. одновременно может быть активно несколько конфигураций.
Реализовывать такую софтины с помощью синглетона, ИМО, весьма не удобно.

ПС>* Heap (распределитель памяти). Это глобальный объект в рамках процесса. Доступен всем (malloc/free, new/delete). Кстати, помимо C/C++ heap-а, имеется глобальный process heap от Windows (WinAPI-шная функция GetProcessHeap возвращает handle этого heap-а).

Хипов может быть несколько. А кроме того этого довольно часто это становится узким местом в многопоточных приложениях (особенно если кто-то, как в STLPort, додумается использовать Sleep для реализации мьютексов). И хотелось бы иметь возможность иметь Хип на поток.
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re[3]: Singleton
От: Пётр Седов Россия  
Дата: 26.06.07 20:30
Оценка:
Здравствуйте, AndrewJD, Вы писали:

ПС>>Пуристы, наверное. Они любят догмы.

AJD>Скорее практики
Догмы и категоричные утверждения свойственны именно теоретикам. Практики в каждом конкретном случае смотрят, подходит ли приём хорошо для решения задачи.
Классическая догма – «goto considered harmful». Но, к примеру, Фредерик Брукс в книге «Мифический человеко-месяц»
Автор(ы): Фредерик Брукс
Эта книга — юбилейное (дополненное и исправленное) издание своего рода библии для разработчиков программного обеспечения во всем мире, написанное Бруксом еще в 1975 году. Тогда же книга была издана на русском языке и давно уже стала библиографической редкостью. В США полагают, что без прочтения книги Брукса не может состояться ни один крупный руководитель программного проекта. Если вы никогда не слышали об этой книге, вы можете поискать ссылки на нее в Интернете (Frederick P. Brooks, The Mythical Man-Month). Вам все сразу станет понятно.
пишет: «… некоторые догматически избегают всех GO TO, что представляется чрезмерным.» (Глава 13. Целое и части > Проектирование без ошибок > Структурное программирование).

ПС>>На самом деле singleton – вполне нормальное решение. Несколько примеров:

ПС>>* Windows-реестр. Это глобальный объект в рамках компьютера. Доступен всем. Используется, к примеру, для регистрации COM-компонентов.
AJD>Опять же все зависит от точки зрения.
Вне зависимости от точки зрения, реестр – глобальный объект в рамках компьютера (точнее, в рамках экземпляра Windows; на компьютере может быть установлено несколько Windows, и каждый будет иметь свой отдельный реестр).

AJD>Действительно можно сказать, что реестр он глобальный в рамках компьютера

Только так и можно сказать (в том смысле, что все процессы, работающие под управлением Windows, обращаются к одному и тому же реестру; изменения, внесённые одним процессом, становятся видны остальным процессам).

AJD>и напрямую вызывать API. Но можно скзать что это конфигурация и где она хранится не имеет значения. Что мешает иметь несколько конфигураций одновременно?

Мы обсуждаем реестр или config? Если config, то действительно полезно изолировать реализацию, чтобы наружу из config-а не торчали уши реестра. Тогда эту реализацию можно безболезненно заменить и хранить данные config-а не в реестре, а, скажем, в текстовом файле (XML, …).

AJD>Простой пример из жизни. Есть софтина которая позволяет работать с несколькими торговыми системами одновременно. Т.е. одновременно может быть активно несколько конфигураций.

AJD>Реализовывать такую софтины с помощью синглетона, ИМО, весьма не удобно.
Никто и не утверждает, что singleton хорошо подходит во всех случаях. Если нужно несколько конфигураций одновременно, то понятно, что config вряд ли стоит делать singleton-ом.
Просто Graf Alex в корневом сообщении
Автор: Graf Alex
Дата: 14.06.07
задал примерно такой вопрос: «Singleton – это зло?». Я ответил, что не зло, и привёл примеры более/менее удачных (по-моему) singleton-ов. То есть показал, что в некоторых (но не всех) случаях singleton хорошо подходит.

ПС>>* Heap (распределитель памяти). Это глобальный объект в рамках процесса. Доступен всем (malloc/free, new/delete). Кстати, помимо C/C++ heap-а, имеется глобальный process heap от Windows (WinAPI-шная функция GetProcessHeap возвращает handle этого heap-а).

AJD>Хипов может быть несколько.
Опять же, никто не утверждает, что в процессе должен быть ровно один heap. Просто удобно, чтобы в любой точке кода был доступен распределитель памяти (у которого, вероятно, pool-ы уже прогреты и выделение блока памяти сводится к просто доставанию оного из подходящего pool-а). Но иногда действительно полезно параметризовать распределение памяти. Например, STL-контейнеры позволяют это (хотя и криво
Автор: Пётр Седов
Дата: 30.10.06
).
Кстати, распределитель виртуальной памяти (доступный через WinAPI-шные функции VirtualAlloc, VirtualFree) принципиально один на весь процесс.

AJD>А кроме того этого довольно часто это становится узким местом в многопоточных приложениях (особенно если кто-то, как в STLPort, додумается использовать Sleep для реализации мьютексов). И хотелось бы иметь возможность иметь Хип на поток.

То есть singleton в рамках потока (thread).
Пётр Седов (ушёл с RSDN)
Re[4]: Singleton
От: AndrewJD США  
Дата: 27.06.07 09:08
Оценка:
Здравствуйте, Пётр Седов, Вы писали:

ПС>Догмы и категоричные утверждения свойственны именно теоретикам.


Лично я пришел к этому выводу путем набивания шишек .

ПС>Практики в каждом конкретном случае смотрят, подходит ли приём хорошо для решения задачи.

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

ПС>Вне зависимости от точки зрения, реестр – глобальный объект в рамках компьютера (точнее, в рамках экземпляра Windows; на компьютере может быть установлено несколько Windows, и каждый будет иметь свой отдельный реестр).

Это с точки зрения пользователя. Например ОС может рассматривать реестр как набор из файлов (покрайней мере 2 файлов)

ПС>Мы обсуждаем реестр или config? Если config, то действительно полезно изолировать реализацию, чтобы наружу из config-а не торчали уши реестра. Тогда эту реализацию можно безболезненно заменить и хранить данные config-а не в реестре, а, скажем, в текстовом файле (XML, …).

Так про то и речь, что в большинстве случаев стоит рассматривать config, а не реестр. Если ты конечно не пишешь сотину для работы с реестром .

ПС>Никто и не утверждает, что singleton хорошо подходит во всех случаях. Если нужно несколько конфигураций одновременно, то понятно, что config вряд ли стоит делать singleton-ом.

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

ПС>Я ответил, что не зло, и привёл примеры более/менее удачных (по-моему) singleton-ов. То есть показал, что в некоторых (но не всех) случаях singleton хорошо подходит.

Я бы сказал, что нужно три раза подумать прежде чем использовать синглетон, возможно есть более удобное и расширяемое решение.

ПС>Кстати, распределитель виртуальной памяти (доступный через WinAPI-шные функции VirtualAlloc, VirtualFree) принципиально один на весь процесс.

С этим приходится жить. Хотя раньше, когда 32mb памяти было очень много, многие делали свой распределитель виртуальной памяти со свопом на диск.

AJD>>А кроме того этого довольно часто это становится узким местом в многопоточных приложениях (особенно если кто-то, как в STLPort, додумается использовать Sleep для реализации мьютексов). И хотелось бы иметь возможность иметь Хип на поток.

ПС>То есть singleton в рамках потока (thread).
Да. Но в рамках потока это уже значительно лучше чем один на процесс
"For every complex problem, there is a solution that is simple, neat,
and wrong."
Re: подскажите паттерн №2
От: MaximVK Россия  
Дата: 27.06.07 11:21
Оценка: 3 (1) +1
Здравствуйте, Graf Alex, Вы писали:

GA>Пару вопросов по паттернам...


GA>вопрос 1: отвлеченный от дела... чисто для научного интереса...

GA>Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?

Я сейчас крамольную мысль скажу. С моей точки зрения, основной недостаток синглетона в том, что он оказался первым паттерном в знаменитой книжке и стал эдаким введением в оные. Как результат: юное дарование услышав, что лишь сакральное знание о паттернах способно за 24 часа из junior-а сделать senior-а, бежит в магазин покупать очередное "Введение в паттерны", чтобы прочитать первые 5-10 страниц, т.к. на большее его не хватает. Не пройдет и дня, как дарование уже внедряет прочитанное в жизнь, паралелльно добавляя в свое резюме "Богатый опыт применения паттернов" и приписывая к "Зарплатным ожиданиям" еще 500 долларов. И все бы ничего, да вот на первых 5-10 страницах любого "Введения в" у нас как раз и располагается описание синглетона. Простого и понятного, как глобальная переменная, а потому и настолько же опасного. Ведь фабрикой, оберткой и прочими мостами проект попортить значительно труднее, да и придумать, куда их впихнуть тоже нелегко.

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

GA>Вопрос 2:

GA>Хотелось бы предоставить пользователю моей библиотеки иметь возможность заменять мои модули своими, но при этом если пользователь ничего не заменял используются реализации из моей библиотеки.
Как тебе совершенно правильно сказали, у тебя получаются обыкновенные плагины. То что написал, можно перефразировать "Мне нужно контролировать создание инстанса модуля, по следующему правилу. Если есть пользовательская реализация модуля — использовать ее, в противном случае — использовать реализацию по умолчанию". Чтобы эффективно контролировать создание инстанса модуля, нужно, чтобы любая попытка создания инстанса модуля проходила через это правило. В самом простом варианте — это обыкновенный фабричный метод.
Re[5]: Singleton
От: Пётр Седов Россия  
Дата: 28.06.07 00:44
Оценка:
Здравствуйте, AndrewJD, Вы писали:

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

Например, есть такой код:
int GetNumFromEdit(HWND hEdit)
{
  assert(IsWindow(hEdit));
  int Len = GetWindowTextLength(hEdit);
  char* pBuf = new char[Len + 1];
  GetWindowText(hEdit, pBuf, Len + 1);
  int Num = atoi(pBuf); // parsing ламерский, но сейчас речь не о нём
  delete[] pBuf;
  return Num;
}

Этот код использует C/C++ heap (new/delete). C/C++ heap – это singleton в рамках процесса (считая, что App.exe и все DLL-и используют один и тот же C/C++ run-time, например msvcrt.dll). Можно отказаться от singleton-а и параметризовать распределение памяти:
class MemManager
{
public:
  virtual void* AllocBlock(int Size) = 0;
  virtual void FreeBlock(void* pMem, int Size) = 0;
};

int GetNumFromEdit2(HWND hEdit, MemManager* pMemManager)
{
  assert(IsWindow(hEdit));
  int Len = GetWindowTextLength(hEdit);
  char* pBuf = static_cast<char*>(pMemManager->AllocBlock((Len + 1) * sizeof(char)));
  GetWindowText(hEdit, pBuf, Len + 1);
  int Num = atoi(pBuf);
  pMemManager->FreeBlock(pBuf, (Len + 1) * sizeof(char));
  return Num;
}

Но тогда деталь реализации (функция создаёт временный буфер) проникает в интерфейс. То есть было одно «зло» (использование глобального C/C++ heap-а), стало другое «зло» (из интерфейса торчат уши реализации). Поэтому отказ от singleton-а – не обязательно хорошо.
Ещё пример. Стандартная библиотека C/C++ предоставляет генератор случайных чисел (доступный через функции srand, rand). Это singleton в рамках потока (считая, что App.exe и все DLL-и используют один и тот же C/C++ run-time). С точки зрения теории это ужас, но на практике вполне годится для простых применений (которых, по-моему, большинство).

ПС>>Вне зависимости от точки зрения, реестр – глобальный объект в рамках компьютера (точнее, в рамках экземпляра Windows; на компьютере может быть установлено несколько Windows, и каждый будет иметь свой отдельный реестр).

AJD>Это с точки зрения пользователя.
И с точки зрения программ, использующих реестр, тоже.

AJD>Например ОС может рассматривать реестр как набор из файлов (покрайней мере 2 файлов)

Реализация реестра (как он там на диске хранится) – дело вторичное. Главное, что реестр – глобальный объект в единственном экземпляре (в рамках экземпляра Windows).

ПС>>Мы обсуждаем реестр или config? Если config, то действительно полезно изолировать реализацию, чтобы наружу из config-а не торчали уши реестра. Тогда эту реализацию можно безболезненно заменить и хранить данные config-а не в реестре, а, скажем, в текстовом файле (XML, …).

AJD>Так про то и речь, что в большинстве случаев стоит рассматривать config, а не реестр.
Я здесь
Автор: Пётр Седов
Дата: 22.06.07
привёл реестр не как удачное место для хранения данных config-а, а как пример более/менее удачного singleton-а. Ведь иногда нужно именно глобальное хранилище. Например, для регистрации COM-компонентов. Ещё пример singleton-а в рамках Windows: running object table (ROT) в COM. RegisterActiveObject:

The RegisterActiveObject function registers the object to which punk points as the active object for the class denoted by rclsid. Registration causes the object to be listed in the running object table (ROT) of OLE, a globally accessible lookup table that keeps track of objects that are currently running on the computer.


ПС>>Никто и не утверждает, что singleton хорошо подходит во всех случаях. Если нужно несколько конфигураций одновременно, то понятно, что config вряд ли стоит делать singleton-ом.

AJD>Фишка в том что часто-густо, при проектировании на это забивают.
Часто забивают на проектирование и пишут в плену framework-а (MFC, VCL, WinForms).

ПС>>Я ответил, что не зло, и привёл примеры более/менее удачных (по-моему) singleton-ов. То есть показал, что в некоторых (но не всех) случаях singleton хорошо подходит.

AJD>Я бы сказал, что нужно три раза подумать прежде чем использовать синглетон, возможно есть более удобное и расширяемое решение.
По-моему, singleton и есть удобное решение. Не нужно всюду таскать ссылку на объект, так как он доступен везде.

ПС>>Кстати, распределитель виртуальной памяти (доступный через WinAPI-шные функции VirtualAlloc, VirtualFree) принципиально один на весь процесс.

AJD>С этим приходится жить.
Разве с этим тяжело жить? Раз модель памяти плоская (flat), то иначе вроде и не сделать .

AJD>Хотя раньше, когда 32mb памяти было очень много, многие делали свой распределитель виртуальной памяти со свопом на диск.

И что, в рамках выполняющейся программы (в мире DOS-32 не было слова «процесс» ) жило несколько распределителей виртуальной памяти одновременно? Сомневаюсь. Скорее, распределитель виртуальной памяти так же был singleton-ом.
Кстати, писать свой распределитель виртуальной памяти с подкачкой страниц – это круто . Я во времена DOS-32 просто использовал DJGPP, там была поддержка DPMI.
Пётр Седов (ушёл с RSDN)
Re[2]: Singleton
От: Пётр Седов Россия  
Дата: 28.06.07 01:57
Оценка:
Здравствуйте, MaximVK, Вы писали:

MVK>на первых 5-10 страницах любого "Введения в" у нас как раз и располагается описание синглетона. Простого и понятного, как глобальная переменная,

Глобальная переменная и глобальный объект – это очень разные вещи. В отличие от переменной, работа с объектом идёт через интерфейс. Объект может проверять параметры методов и как-то реагировать на неправильные значения (assert, исключение, …).

MVK>а потому и настолько же опасного.

Всё-таки, чем опасен singleton?
Например, стандартная библиотека C++ предоставляет глобальный текстовый поток для вывода (по умолчанию на консоль) – std::cout. В чём опасность?
Например, в библиотеке VCL (Delphi и C++ Builder) есть глобальные объекты Application и Screen (unit forms.pas):

{ Global objects }

var
  Application: TApplication;
  Screen: TScreen;

В чём опасность?
Например, в .NET-е есть глобальный объект System.Text.Encoding.UTF8. В чём опасность?
Пётр Седов (ушёл с RSDN)
Re[3]: Singleton
От: MaximVK Россия  
Дата: 28.06.07 15:43
Оценка: 59 (7) +4
Здравствуйте, Пётр Седов, Вы писали:

ПС>Глобальная переменная и глобальный объект – это очень разные вещи. В отличие от переменной, работа с объектом идёт через интерфейс. Объект может проверять параметры методов и как-то реагировать на неправильные значения (assert, исключение, …).


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

ПС>Всё-таки, чем опасен singleton?

Ну давай по порядку.

1. Есть такое отличное правило SRP (Single Responsibility Principle). Синглетон его нарушает, т.к. кроме своих бизнес задач(например, доступ к конфигу) он еще решает и задачу контролирования количества своих экземпляров. Фабрика, например, от такого недостатка избавлена.
2. Синглетон повышает связность. Недостатки высокой связности я думаю приводить не надо.
3. Наличие синглетонов понижает тестируемость(testability). Отчасти это следствие второго пункта. Но есть и еще одна причина. Если синглетон предоставляет интерфейс для изменения своего состояния, то тесты теряют независимость друг от друга, т.к. результат работы одного теста может повлиять на результат другого.
4. Еще приводят аргумент, что синглетон противоречит LSP. Для меня этот довод несколько спорный, поэтому я пишу этот пункт только по причине частого его упоминания.

Теперь к моему посту. Почему я считаю, что синглетон нужно приводить в конце книги. Бесспорно, синглетон — это инструмент. Как и любой другой инструмент, он может быть использован по месту(хотя для меня это всегда некоторый smell). Главный его недостаток, что он потворствует плохому дизайну. И действительно, зачем строить компонентую модель, разбираться с сервис провайдерами, читать про какую-то dependency injection, когда можно на раз-два свалять синглетон, да еще и с double checking locking в GetInstance. Практика показывает, что если немного подумать, то в большинстве случаев можно избежать использование синглетонов. Да, в краткосрочной перспективе — вы потеряете время "на подумать", но в долгосрочной — выиграете. Увы, понимают это далеко не все. Потому что синглетон — это паттерн, а паттерны — это круто.
Re[4]: Singleton
От: Пётр Седов Россия  
Дата: 30.06.07 12:41
Оценка: -1
Здравствуйте, MaximVK, Вы писали:

ПС>>Глобальная переменная и глобальный объект – это очень разные вещи. В отличие от переменной, работа с объектом идёт через интерфейс. Объект может проверять параметры методов и как-то реагировать на неправильные значения (assert, исключение, …).

MVK>Если у синглетона есть состояние и определен интерфейс для изменения этого состояние, то все эти различиянесущественны с точки зрения возникающей проблемы.
Различие между глобальным объектом и «эквивалентной» кучкой глобальных переменных очень существенно. Например, есть глобальный объект:
class Item
{
public:
  static void Cache_AddRef();
  static void Cache_Release();
  ...
// реализация
private:
  static int s_CacheRefs;
  ...
};

int Item::s_CacheRefs = 0;
...

inline void Item::Cache_AddRef()
{
  s_CacheRefs++;
}

inline void Item::Cache_Release()
{
  s_CacheRefs--;
}

По всему коду разбросаны вызовы Cache_AddRef/Cache_Release:
void UseItem()
{
  Item::Cache_AddRef();
  ...
  Item::Cache_Release();
}

Можно вместо глобального объекта сделать «эквивалентную» кучку глобальных переменных:
int g_Item_CacheRefs = 0;
...

и писать по всему коду:
void UseItem()
{
  g_Item_CacheRefs++;
  ...
  g_Item_CacheRefs--;
}

Теперь нужно добавить многопоточность. Вариант с глобальным объектом переносит это более/менее безболезненно (так как интерфейс отделён от реализации):
inline void Item::Cache_AddRef()
{
  InterlockedIncrement(&s_CacheRefs);
}

inline void Item::Cache_Release()
{
  InterlockedDecrement(&s_CacheRefs);
}

или так:
#ifdef _DEBUG
const DWORD Item::s_HomeThreadId = GetCurrentThreadId();
#endif

inline void Item::Cache_AddRef()
{
#ifdef _DEBUG
  assert(GetCurrentThreadId() == s_HomeThreadId);
#endif
  s_CacheRefs++;
}

inline void Item::Cache_Release()
{
#ifdef _DEBUG
  assert(GetCurrentThreadId() == s_HomeThreadId);
#endif
  s_CacheRefs--;
}

или так:
inline void Item::Cache_AddRef()
{
  assert(s_TLSIndex != -1);
  ThreadState* pState = static_cast<ThreadState*>(TlsGetValue(s_TLSIndex));
  assert(pState != NULL);
  pState->CacheRefs++;
}

inline void Item::Cache_Release()
{
  assert(s_TLSIndex != -1);
  ThreadState* pState = static_cast<ThreadState*>(TlsGetValue(s_TLSIndex));
  assert(pState != NULL);
  pState->CacheRefs--;
}

В варианте с «эквивалентной» кучкой глобальных переменных придётся пройтись по всему коду и заменить «g_Item_CacheRefs++» и «g_Item_CacheRefs--» на что-нибудь другое.
То есть глобальный объект – это гораздо лучше, чем «эквивалентная» кучка глобальных переменных. Поэтому с утверждением «глобальные переменные – зло» я более/менее согласен, а вот с утверждением «глобальный объект – зло» я не согласен.

MVK>И ассертами с исключениями ты тут ровным ничего не сделаешь.

С помощью assert-ов и исключений можно проверять правильность использования глобального объекта (например, требование использовать объект только в рамках одного потока).

MVK>1. Есть такое отличное правило SRP (Single Responsibility Principle). Синглетон его нарушает, т.к. кроме своих бизнес задач(например, доступ к конфигу) он еще решает и задачу контролирования количества своих экземпляров.

То есть singleton плох из-за единственности экземпляра? «Я дерусь, потому что я дерусь!» © Портос.
Как реализовать единственность экземпляра – это дело вторичное. Например, в Scala можно писать не класс, а именно объект (здесь
Автор(ы): Martin Odersky, Philippe Altherr, Vincent Cremet, Burak Emir, Sebastian Maneth, Stephane Micheloud, Nikolay Mihaylov, Michel Schinz, Erik Stenman, Matthias Zenger, http://scala.epfl.ch
Дата: 22.05.2005
Язык Scala был создан в 2001-2004 гг в лаборатории методов программирования EPFL. Он стал результатом исследований, направленных на разработку более хорошей языковой поддержки компонентного ПО. С помощью Scala мы хотели бы проверить две гипотезы. Во-первых, мы считаем, что язык программирования компонентного ПО должен быть масштабируемым в том смысле, что должна быть возможность с помощью одних и тех же концепций описать как маленькие, так и большие части. Поэтому мы сконцентрировались на механизмах абстракции, композиции и декомпозиции вместо введения большого количества примитивов, которые могут быть полезными только на каком-то одном уровне масштабирования. Во-вторых, мы считаем, что масштабируемая поддержка компонентов может быть предоставлена языком программирования, унифицирующим и обобщающим объектно-ориентированное и функциональное программирование.
):

// Java
class PrintOptions
{
  public static void main(...)
  {
    ...
  }
}

// Scala
object PrintOptions
{
  def main(...) : unit =
  {
    ...
  }
}


* В Scala кроме определений классов есть определения объектов (начинающиеся с object). Определения объектов определяют класс с одним экземпляром – который иногда называют singleton-объектом. В приведенном примере singleton-объект PrintOptions содержит функцию-член main. Singleton-объекты в Scala заменяют статические части Java-классов.

Да и в C++ не обязательно извращаться с GetInstance и private-изацией конструктора. Вполне сойдёт класс, в котором все члены static, например:
class Clock
{
public:
  static void Init();
  static double Time();
// реализация
private:
  static double s_SecsPerTick;
  static LARGE_INTEGER s_InitTicks;
};

double Clock::s_SecsPerTick = 0;
LARGE_INTEGER Clock::s_InitTicks;

inline void Clock::Init()
{
  assert(s_SecsPerTick == 0);
  LARGE_INTEGER TicksPerSec;
  QueryPerformanceFrequency(&TicksPerSec);
  s_SecsPerTick = 1.0 / static_cast<double>(TicksPerSec.QuadPart);
  QueryPerformanceCounter(&s_InitTicks);
}

// возвращает время в секундах с момента Clock::Init
inline double Clock::Time()
{
  assert(s_SecsPerTick != 0); // должен быть вызван Clock::Init
  LARGE_INTEGER t;
  QueryPerformanceCounter(&t);
  return s_SecsPerTick * (t.QuadPart - s_InitTicks.QuadPart);
}


MVK>2. Синглетон повышает связность.

У низкой связности (coupling) тоже есть недостатки:
* Куча интерфейс-классов (как MemManager здесь
Автор: Пётр Седов
Дата: 28.06.07
) усложняет код.
* Снижается эффективность программы (из-за косвенности).

MVK>3. Наличие синглетонов понижает тестируемость(testability). Отчасти это следствие второго пункта. Но есть и еще одна причина. Если синглетон предоставляет интерфейс для изменения своего состояния, то тесты теряют независимость друг от друга, т.к. результат работы одного теста может повлиять на результат другого.

Во-первых, не всё можно тестировать (например, как тестировать Clock?).
Во-вторых, если нужно тестировать сам глобальный объект и он реализован без GetInstance (который на самом деле не нужен), то можно закрыть глаза на требование единственности экземпляра и тестировать независимых близнецов singleton-а, например:
class Item
{
  // неявное копирование запрещено
  Item(const Item& Source); // нет реализации
  Item& operator=(const Item& Source); // нет реализации
public:
  void Cache_AddRefs();
  void Cache_Release();
  ...
// реализация
private:
  int m_CacheRefs;
  ...
};
Item g_Item; // singleton

void TestItem()
{
  // тест 1
  {
    Item it; // независимый близнец singleton-а
    ...
  }

  // тест 2
  {
    Item it;
    ...
  }

  ...
}

В-третьих, есть сомнения по поводу самих тестов, например «Что не так с юнит-тестами»
Автор: Lazy Cjow Rhrr
Дата: 26.07.06
.

MVK>4. Еще приводят аргумент, что синглетон противоречит LSP. Для меня этот довод несколько спорный, поэтому я пишу этот пункт только по причине частого его упоминания.

Liskov's substitution principle гласит (здесь):

All classes derived from a base class should be interchangeable when used as a base class.

По-моему, более/менее удачные singleton-ы не нарушают LSP. Например, в стандартной библиотеке C++ есть глобальный объект std::cout, наследующий от класса std::ostream (строго говоря, это не класс, а typedef). Есть функция WriteExp (здесь
Автор: Пётр Седов
Дата: 20.06.07
):
void WriteExp(ostream* pStream, const Exp* pExp)
{
  ...
}

Эта функция пишет выражение в поток. Я вполне могу подсунуть этой функции std::cout:
Exp* pFormula = ...;
WriteExp(&cout, pFormula);


MVK>Бесспорно, синглетон — это инструмент. … Главный его недостаток, что он потворствует плохому дизайну.

Иногда отказ от singleton-а приводит к тому, что из интерфейса торчат уши реализации. Вот это действительно плохой дизайн. Например, есть функция CalcSomething:
double CalcSomething(параметры вычисления)
{
  double Res = 0;
  for (...)
  {
    ...
    Res += ...;
  }
  return Res;
}

Если эта функция выдаёт ошибочный результат, то можно сделать отладку в стиле printf:
#include <iostream> // => в любой точке кода доступен std::cout

double CalcSomething(параметры вычисления)
{
  double Res = 0;
  for (...)
  {
    ...
    Res += ...;
    std::cout << "Res = " << Res << std::endl;
  }
  return Res;
}

Это возможно, так как std::cout – singleton, он доступен глобально. Если отказаться от singleton-а, то придётся сделать как-то так:
#include <iostream>

double CalcSomething(параметры вычисления, std::ostream* pDebugStream)
{
  double Res = 0;
  for (...)
  {
    ...
    Res += ...;
    *pDebugStream << "Res = " << Res << std::endl;
  }
  return Res;
}

(Вместо std::ostream можно использовать абстрактный интерфейс-класс.) Но это хуже, чем жёстко использовать std::cout. Во-первых, деталь реализации (отладочная печать) проникает в интерфейс (появился дополнительный параметр). То есть из интерфейса торчат уши реализации. Во-вторых, нужно пройтись по всем местам, где вызывается CalcSomething, и снабдить вызов каким-то потоком (скорее всего, тем же std::cout-ом). Кстати, это относится не только к std::cout, но и к аналогичным вещам (printf, System.Console, OutputDebugString, ATLTRACE, …).

MVK>И действительно, зачем строить компонентую модель, разбираться с сервис провайдерами, читать про какую-то dependency injection,

Да, зачем нагнетать сложность? По-моему, здесь
Автор:
Дата: 28.10.06
хорошо написано:

Мой жизненный опыт говорит о том, что сложности придумывают в двух случаях:
1) Не умеют работать
2) Раздувают объём работ
Ну и как дополнительный вариант — комбинация этих двух.


MVK>когда можно на раз-два свалять синглетон,

По-моему, начинать надо с простых решений, а усложнять только по необходимости.

MVK>да еще и с double checking locking в GetInstance.

Зачем такие сложности? Я привёл пример singleton-а (Clock), там всё гораздо проще.

MVK>Практика показывает, что если немного подумать, то в большинстве случаев можно избежать использование синглетонов. Да, в краткосрочной перспективе — вы потеряете время "на подумать", но в долгосрочной — выиграете.

Нет такого выбора, что либо «не думая по-быстрому сделать singleton», либо «подумать и сделать не singleton». Есть выбор либо «подумать и сделать singleton», либо «подумать и сделать не singleton». То есть подумать надо обязательно.

MVK>Увы, понимают это далеко не все.

Не все согласны с утверждением «singleton – зло».
Пётр Седов (ушёл с RSDN)
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.