вопрос 1: отвлеченный от дела... чисто для научного интереса...
Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?
Вопрос 2:
Пишу библиотеку. В ней есть ModuleA, ModuleB, ModuleC. Для каждого модуля объявлен интерфейс, который в принципе укладывается в одну функцию типа SomeResultType runModuleA(<parameters>); Хотя как интерфейс с одним виртуальным методом тоже возможен.
Реализации модулей понятно друг о друге ничего не знают и общаются только через интерфейсы.
Хотелось бы предоставить пользователю моей библиотеки иметь возможность заменять мои модули своими, но при этом если пользователь ничего не заменял используются реализации из моей библиотеки.
Проще говоря пускай ModuleA и ModuleB вызывают ModuleC в своей реализации.
Хотелось код вызова писать так:
Т.е. по сути пользователь должен как то регистрировать свой модуль.
Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...
Здравствуйте, 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>Т.е. по сути пользователь должен как то регистрировать свой модуль.
А как твоя софтина без регистрации узнает, что есть еще какие-то модули? Посмотри на тот же СОМ — приходжится регистрится, чтобы твои интерфейсы и реализации были доступны.
GA>Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...
Трудно тебе что-либо посоветовать, потому как задача ну очень вобщем поставлена.
З.Ы. Вообще для того, чтобы можно было вот так вот прозрачно подменять реализации без регистрации придется укладывать твои модули в некоторое Прокрустово ложэ — например сказать, что есть папка, в эту папку пользователь должен положить свою длл-ну, при инициализации софтина сканит папку, грузит длл-ну и получает указатель на реализацию пользователя. Ну и соотвественно интерфейс этих длл-ин должен быть унифицирован. Короче говоря, тот же СОМ, только руками.
Здравствуйте, 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>Трудно тебе что-либо посоветовать, потому как задача ну очень вобщем поставлена.
Короче говоря, какие есть механизмы, которые позволят юзеру потставлять вместо дефолтных модулей свои?
Здравствуйте, Graf Alex, Вы писали:
GA>Пару вопросов по паттернам...
спроси гугл на тему "IOC контейнеры" — как раз то что описано, даже еще более обобщенно. Но в С++ я такого не видел.
Здравствуйте, dotidot, Вы писали:
D>спроси гугл на тему "IOC контейнеры" — как раз то что описано, даже еще более обобщенно. Но в С++ я такого не видел.
ну или более приземлено: "plugin architecture"
Re[3]: подскажите паттерн №2
От:
Аноним
Дата:
15.06.07 07:09
Оценка:
GA>Короче говоря, какие есть механизмы, которые позволят юзеру потставлять вместо дефолтных модулей свои?
Ну первое что приходит на ум — реализуйте у себя COM
Здравствуйте, Graf Alex, Вы писали:
GA>Обычно я пользуюсь синглтоном как единственным объектом... Мне в общем то пофиг что он доступен везде... GA>Просто хотел услышать мнение окружающих...
Модуль должен на вход получать внешние объекты (через ссылки/указатели/интерфейсы — не суть). О том, что провайдером данного объекта является синглтон, модулю знать не надо, иначе будут траблы при юнит-тестировании или рефакторинге. А так — подсунул модулю на вход какой-то другой инстанс (не от синглтона, а тот же мок-объект, например) — и все. Никаких редактирований кода, зависимостей и т.п.
Здравствуйте, dotidot, Вы писали:
D>Здравствуйте, dotidot, Вы писали:
D>>спроси гугл на тему "IOC контейнеры" — как раз то что описано, даже еще более обобщенно. Но в С++ я такого не видел. D>ну или более приземлено: "plugin architecture"
IoC это автоматичиское разруливание зависимостей между доменными объектами, а то что автору нужно скорее плагинная архитектура.
Второй вопрос распадается на два: Как сделать архитектурно?
Как вариант, можно сделать конфигурационный файл, в котором записано какие модули следует загружать и из какого файла. То есть,к примеру, в конфигурационном фале пишутся так называемые "точки входа". То есть места в библиотеке, куда может быть загружен внешний модуль, реализующий определённый интерфейс. В дефолтовом конфигурационном фале прописываются твои модули, юзер может вместо них зарегистрировать свои.
Как реализовать технически?
Можно сделать через COM, то есть в точке входа прописывается guid или progId ком объекта, реализующего заданный интерфейс. Можно обойтись без кома — в каждой дллке будет функция ICustomMidule* Load(), возвращающая интерфейсный указатель. Если дллки динамически линкуются с CRT, то можно смело при необходимости использовать такой код:
Соответсвенно, всяки shared_ptr<> вполне актуальны. Ограничение — плагины и библиототека должны быть скомпилированы одним и тем же компилятором. При статической линковке надо кроме Load делать ещё и Free в дллке, то есть что-то типа
Free(someModule);
Ещё как вариант — возвращать не интефрейс, а handle объекта из дллки и, соответсвенно, использовать голый С. Это позволит получить более переносимое решение. Пример кода:
Здравствуйте, Graf Alex, Вы писали:
GA>Какие есть подходящие решения? Можно было бы конечно воспользоваться дефолтными параметрами, но как то некультурно получается. Да и параметров может быть наверное тоже много — какие то дефолтные, какие то нет...
Здравствуйте, 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).
Здравствуйте, Пётр Седов, Вы писали:
ПС>* 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» ).
Здравствуйте, Пётр Седов, Вы писали:
ПС>Пуристы, наверное. Они любят догмы.
Скорее практики
ПС>На самом деле 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."
Здравствуйте, AndrewJD, Вы писали:
ПС>>Пуристы, наверное. Они любят догмы. AJD>Скорее практики
Догмы и категоричные утверждения свойственны именно теоретикам. Практики в каждом конкретном случае смотрят, подходит ли приём хорошо для решения задачи.
Классическая догма – «goto considered harmful». Но, к примеру, Фредерик Брукс в книге «Мифический человеко-месяц»
пишет: «… некоторые догматически избегают всех 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 в корневом сообщении
задал примерно такой вопрос: «Singleton – это зло?». Я ответил, что не зло, и привёл примеры более/менее удачных (по-моему) singleton-ов. То есть показал, что в некоторых (но не всех) случаях singleton хорошо подходит.
ПС>>* Heap (распределитель памяти). Это глобальный объект в рамках процесса. Доступен всем (malloc/free, new/delete). Кстати, помимо C/C++ heap-а, имеется глобальный process heap от Windows (WinAPI-шная функция GetProcessHeap возвращает handle этого heap-а). AJD>Хипов может быть несколько.
Опять же, никто не утверждает, что в процессе должен быть ровно один heap. Просто удобно, чтобы в любой точке кода был доступен распределитель памяти (у которого, вероятно, pool-ы уже прогреты и выделение блока памяти сводится к просто доставанию оного из подходящего pool-а). Но иногда действительно полезно параметризовать распределение памяти. Например, STL-контейнеры позволяют это (хотя и криво
).
Кстати, распределитель виртуальной памяти (доступный через WinAPI-шные функции VirtualAlloc, VirtualFree) принципиально один на весь процесс.
AJD>А кроме того этого довольно часто это становится узким местом в многопоточных приложениях (особенно если кто-то, как в STLPort, додумается использовать Sleep для реализации мьютексов). И хотелось бы иметь возможность иметь Хип на поток.
То есть singleton в рамках потока (thread).
Здравствуйте, Пётр Седов, Вы писали:
ПС>Догмы и категоричные утверждения свойственны именно теоретикам.
Лично я пришел к этому выводу путем набивания шишек .
ПС>Практики в каждом конкретном случае смотрят, подходит ли приём хорошо для решения задачи.
Безусловно. Проблема в том, что когда продукт развивается, когда-то простые, хорошо подходящие приемы превращаются в геморрой при сопровождении. Разумется, это касается не только синглтонов.
Опять же из практики я пришел к выводу, что глобальные, неявные зависимости от реализации это очень и очень плохо. Использывать же синглетон как средство контролирования количества экземпляров без предоставления глобального доступа не имеет особого смысла.
ПС>Вне зависимости от точки зрения, реестр – глобальный объект в рамках компьютера (точнее, в рамках экземпляра 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."
Здравствуйте, Graf Alex, Вы писали:
GA>Пару вопросов по паттернам...
GA>вопрос 1: отвлеченный от дела... чисто для научного интереса... GA>Слышал, что Singletone уже некоторые считают антипаттерном. Мол что это такое: объект доступен из всей программы? А что сейчас вместо синглтона модно использовать?
Я сейчас крамольную мысль скажу. С моей точки зрения, основной недостаток синглетона в том, что он оказался первым паттерном в знаменитой книжке и стал эдаким введением в оные. Как результат: юное дарование услышав, что лишь сакральное знание о паттернах способно за 24 часа из junior-а сделать senior-а, бежит в магазин покупать очередное "Введение в паттерны", чтобы прочитать первые 5-10 страниц, т.к. на большее его не хватает. Не пройдет и дня, как дарование уже внедряет прочитанное в жизнь, паралелльно добавляя в свое резюме "Богатый опыт применения паттернов" и приписывая к "Зарплатным ожиданиям" еще 500 долларов. И все бы ничего, да вот на первых 5-10 страницах любого "Введения в" у нас как раз и располагается описание синглетона. Простого и понятного, как глобальная переменная, а потому и настолько же опасного. Ведь фабрикой, оберткой и прочими мостами проект попортить значительно труднее, да и придумать, куда их впихнуть тоже нелегко.
В действительности же описание синглетона надо было поместить в середину или даже в самый конец книжки, чтобы до него добрались лишь старательные читатели, среди которых процент "шапкозакидателей" стремиться к нулю.
GA>Вопрос 2: GA>Хотелось бы предоставить пользователю моей библиотеки иметь возможность заменять мои модули своими, но при этом если пользователь ничего не заменял используются реализации из моей библиотеки.
Как тебе совершенно правильно сказали, у тебя получаются обыкновенные плагины. То что написал, можно перефразировать "Мне нужно контролировать создание инстанса модуля, по следующему правилу. Если есть пользовательская реализация модуля — использовать ее, в противном случае — использовать реализацию по умолчанию". Чтобы эффективно контролировать создание инстанса модуля, нужно, чтобы любая попытка создания инстанса модуля проходила через это правило. В самом простом варианте — это обыкновенный фабричный метод.
Здравствуйте, AndrewJD, Вы писали:
AJD>Опять же из практики я пришел к выводу, что глобальные, неявные зависимости от реализации это очень и очень плохо.
Например, есть такой код:
int GetNumFromEdit(HWND hEdit)
{
assert(IsWindow(hEdit));
int Len = GetWindowTextLength(hEdit);
char* pBuf = newchar[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, а не реестр.
Я здесь
привёл реестр не как удачное место для хранения данных 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.
Здравствуйте, 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. В чём опасность?
Здравствуйте, Пётр Седов, Вы писали:
ПС>Глобальная переменная и глобальный объект – это очень разные вещи. В отличие от переменной, работа с объектом идёт через интерфейс. Объект может проверять параметры методов и как-то реагировать на неправильные значения (assert, исключение, …).
Если у синглетона есть состояние и определен интерфейс для изменения этого состояние, то все эти различия — несущественны с точки зрения возникающей проблемы. И ассертами с исключениями ты тут ровным ничего не сделаешь.
ПС>Всё-таки, чем опасен singleton?
Ну давай по порядку.
1. Есть такое отличное правило SRP (Single Responsibility Principle). Синглетон его нарушает, т.к. кроме своих бизнес задач(например, доступ к конфигу) он еще решает и задачу контролирования количества своих экземпляров. Фабрика, например, от такого недостатка избавлена.
2. Синглетон повышает связность. Недостатки высокой связности я думаю приводить не надо.
3. Наличие синглетонов понижает тестируемость(testability). Отчасти это следствие второго пункта. Но есть и еще одна причина. Если синглетон предоставляет интерфейс для изменения своего состояния, то тесты теряют независимость друг от друга, т.к. результат работы одного теста может повлиять на результат другого.
4. Еще приводят аргумент, что синглетон противоречит LSP. Для меня этот довод несколько спорный, поэтому я пишу этот пункт только по причине частого его упоминания.
Теперь к моему посту. Почему я считаю, что синглетон нужно приводить в конце книги. Бесспорно, синглетон — это инструмент. Как и любой другой инструмент, он может быть использован по месту(хотя для меня это всегда некоторый smell). Главный его недостаток, что он потворствует плохому дизайну. И действительно, зачем строить компонентую модель, разбираться с сервис провайдерами, читать про какую-то dependency injection, когда можно на раз-два свалять синглетон, да еще и с double checking locking в GetInstance. Практика показывает, что если немного подумать, то в большинстве случаев можно избежать использование синглетонов. Да, в краткосрочной перспективе — вы потеряете время "на подумать", но в долгосрочной — выиграете. Увы, понимают это далеко не все. Потому что синглетон — это паттерн, а паттерны — это круто.
Здравствуйте, MaximVK, Вы писали:
ПС>>Глобальная переменная и глобальный объект – это очень разные вещи. В отличие от переменной, работа с объектом идёт через интерфейс. Объект может проверять параметры методов и как-то реагировать на неправильные значения (assert, исключение, …). MVK>Если у синглетона есть состояние и определен интерфейс для изменения этого состояние, то все эти различия – несущественны с точки зрения возникающей проблемы.
Различие между глобальным объектом и «эквивалентной» кучкой глобальных переменных очень существенно. Например, есть глобальный объект:
Теперь нужно добавить многопоточность. Вариант с глобальным объектом переносит это более/менее безболезненно (так как интерфейс отделён от реализации):
// 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, например:
) усложняет код.
* Снижается эффективность программы (из-за косвенности).
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; // singletonvoid TestItem()
{
// тест 1
{
Item it; // независимый близнец singleton-а
...
}
// тест 2
{
Item it;
...
}
...
}
.
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 (здесь
Эта функция пишет выражение в поток. Я вполне могу подсунуть этой функции std::cout:
Exp* pFormula = ...;
WriteExp(&cout, pFormula);
MVK>Бесспорно, синглетон — это инструмент. … Главный его недостаток, что он потворствует плохому дизайну.
Иногда отказ от singleton-а приводит к тому, что из интерфейса торчат уши реализации. Вот это действительно плохой дизайн. Например, есть функция CalcSomething:
double CalcSomething(параметры вычисления)
{
double Res = 0;
for (...)
{
...
Res += ...;
}
return Res;
}
Если эта функция выдаёт ошибочный результат, то можно сделать отладку в стиле printf:
#include <iostream> // => в любой точке кода доступен std::coutdouble 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,
Да, зачем нагнетать сложность? По-моему, здесь
Мой жизненный опыт говорит о том, что сложности придумывают в двух случаях:
1) Не умеют работать
2) Раздувают объём работ
Ну и как дополнительный вариант — комбинация этих двух.
MVK>когда можно на раз-два свалять синглетон,
По-моему, начинать надо с простых решений, а усложнять только по необходимости.
MVK>да еще и с double checking locking в GetInstance.
Зачем такие сложности? Я привёл пример singleton-а (Clock), там всё гораздо проще.
MVK>Практика показывает, что если немного подумать, то в большинстве случаев можно избежать использование синглетонов. Да, в краткосрочной перспективе — вы потеряете время "на подумать", но в долгосрочной — выиграете.
Нет такого выбора, что либо «не думая по-быстрому сделать singleton», либо «подумать и сделать не singleton». Есть выбор либо «подумать и сделать singleton», либо «подумать и сделать не singleton». То есть подумать надо обязательно.
MVK>Увы, понимают это далеко не все.
Не все согласны с утверждением «singleton – зло».