![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
SABROG |
|
|||
![]() Hacker ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2481 Регистрация: 18.9.2006 Репутация: 4 Всего: 91 |
Пытаюсь сделать "правильный" дизайн приложения. Есть классы типа Settings, DownloadManager, ScriptManager, JobManager.
И 3 класса должны иметь доступ к Settings, чтобы читать и писать свои данные. Напрашивается синглтон, но мы знаем, что это антипаттерн и поэтому он быстро "распрашивается" обратно. Альтернатива - передача указателя на Settings в конструктор каждого объекта (или через метод setSettings, что менее рекомендуемо). По сути советуют использовать Dependency Injection / Inversion Of Control (IoC) (пока разницы не понял, говорят, что одно частный случай другого). Сам паттерн не ограничивается передачей указателя на экземпляр класса, это должен быть указатель на интерфейс для конкретного класса типа ISettings, чтобы жестко не привязывать объекты друг к другу (забыл как называется это принцип ООП). Собственно на этой стадии реализация паттерна не идеальна (уже не помню почему), поэтому предлагают использовать так называемый контейнер (DI Container). Это разновидность шаблона Service Locator'а, где регистрируется связка КлассИнтерфейса=КонкретныйКласс. Обычно эти контейнеры настолько наворочены, что их выделяют в отдельные фреймворки и библиотеки. Собственно это то к чему я пришел убегая от сиглтона. Почему в рунете так мало информации о паттерне Dependency Injection? А в англоязычных ресурсах информация представлена на 90% с примерами на Java. Я понимаю, что шаблон придумали именно разработчики Java, но неужели проблемы не существует на C++, что почти не реально найти примеры и классы? Что используете в разработке вместо Одиночек? |
|||
|
||||
boostcoder |
|
|||
![]() pattern`щик ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 5458 Регистрация: 1.4.2010 Репутация: 49 Всего: 110 |
я бы предпочел синглтон. хоть это и антипаттерн, но иногда без него никуда.
второй вариант - передавать ссылку на Settings. чтоб не привязываться к типу, тип ссылки можно передать как шаблонный тип. ну или интерфейс(как вы и писали). Это сообщение отредактировал(а) boostcoder - 12.8.2010, 22:55 |
|||
|
||||
JackYF |
|
|||
![]() полуавантюрист ![]() ![]() ![]() ![]() Профиль Группа: Участник Сообщений: 5814 Регистрация: 28.8.2004 Где: страна тысячи озё р Репутация: 18 Всего: 162 |
Передаю, грубо говоря, shared_ptr< [const] Settings > в конструктор. Собственно, не очень понял, что такое Dependency Injection и чем оно лучше даже того же синглтона.
|
|||
|
||||
djamshud |
|
|||
![]() Пердупержденный ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1655 Регистрация: 23.11.2009 Репутация: 8 Всего: 39 |
Предложу анти в кубе не-паттерн: глобальный объект сеттингс. У вас (не теоретически, а практически!) может быть одновременно несколько наборов настроек, параллельно живущих и использующихся во время работы программы? Нет? Тогда плюньте на теоретика Александреску и решайте свою задачу, а не высосанную из пальца проблему.
Добавлено через 37 секунд Впрочем и синглтон в равной степени подходит... -------------------- 'Cuz I never walk away from what I know is right Alice Cooper - Freedom |
|||
|
||||
SABROG |
|
|||
![]() Hacker ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2481 Регистрация: 18.9.2006 Репутация: 4 Всего: 91 |
Лучше тем, что не singleton с его недостатками. В принципе не сложно писать такие цепочки:
Другое дело, что вариант не идеален, а как работают DI Container'ы для C++ я пока не до конца разобрался. |
|||
|
||||
Любитель |
|
|||
Программист-романтик ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 3645 Регистрация: 21.5.2005 Где: Воронеж Репутация: 24 Всего: 92 |
Во-первых, с иок-ом, как таковым, всё просто - не делай зависимости класса A от класса Settings. Делай интерфейс ISettings и работай с ним.
Но, во-вторых, это неудобно - т. к. придётся передавать указатель на объект настроек. "Инъекция" возможна только в том случае, если все объекты мы получаем через DI-контейнер. Т. е. в итоге мы: 1. Регистрируем (где-нить при старте приложения) маппинг: ISettings -> DbSettings, или XmlSettings, или IniSettings (да, вообще логично что Settings - это чисто объект данных, а методы load/save/etc. в SettingsManager-е каком-нибудь, ну да ладно). 2. Добавляем параметр типа ISettings в конструктор нашего класса A (инъекция в конструктор - самая простейшая и достаточно популярная). 3. Вместо создания объекта класса A пишем container.Resolve<A>(). В общем-то всё красиво. У DbSettings у нас будет IDbConnection (я условно), который тоже автоматом зарезольвится на что надо - и т. д. Более того, любой DI-контейнер на практике действительно является и сервис локатором ("чистый" DI-контейнер должен производить только "инъекции", а создание/получение объектов - не его дело, но на практике такого не бывает). А это значит, что мы также можем регулировать время жизни объектов и пр. Теперь проблемы. Любой настоящий DI-контейнер реализуется с использованием рефлекшена. Именно поэтому это (в основном) ява/шарп. В чистом С++ "правильного" DI вообще не может быть. С учёток оберток, предоставляющих метаданные (как в том ж Qt) - в принципе сделать вомзожно, но я не встречал готовых реализаций. В случае С++ на мой взгляд есть 2 варианта: 1. Если нужна действительна абстракция - простейшая фабрика, без каких либо наворотов. 2. Если нет - то синглтон ![]() Более того - даже в случае явы/шарпа я бы не стал городить иерархии классов/интерфейсов и полную абстракцию, если это бы действительно не было бы нужно. |
|||
|
||||
SABROG |
|
||||
![]() Hacker ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2481 Регистрация: 18.9.2006 Репутация: 4 Всего: 91 |
Они есть: QNimbleContainer QtIocContainer Другое дело, что они жестко привязаны к мета-объектной системе и как следствие любой класс должен наследовать QObject. К тому же не хотелось бы использовать сторонние библиотеки/фреймворки ради того, чтобы просто уйти от синглтона.
То есть реализовать Service Locator через фабрику и передавать указатель на IServiceLocator через конструктор в каждый объект, типа этого? Эхх, если начать реализацию синглтона, то помимо идеологических проблем существует и физиологическая - проблема с потоко-безопасностью. Чтобы его сделать потоко-безопасным придется прибегнуть к double checked locking паттерну реализованному через атомарные операции, которых пока нет в C++, это значит надо использовать сторонние библиотеки, BOOST например (ну или начать использовать функции из C++0x) |
||||
|
|||||
Любитель |
|
||||||
Программист-романтик ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 3645 Регистрация: 21.5.2005 Где: Воронеж Репутация: 24 Всего: 92 |
Совсем не впечатлило. Всё делать через плагины - это жёстко. Здесь уже гораздо лучше. Беглый просмотр блога говорит о том, что направление выбрано верное. Но.. насколько это production ready - уже другой вопрос (всё-таки пока похоже больше на грамотное увлечение).
Ну.. так или иначе они будут расчитывать на некоторый базовый класс - для эмуляции рефлекшена (черех собственное хранилище метаданных). Ну или точнее так - в принципе базовый класс не обязательно, но на хранилище метаданных обязательно (ну плюс там ещё проблема универсальной передачи параметров появляется, решаемая в Qt через QVariant).
Ну.. Service Locator реально нужен когда? Когда у нас накапливается много объектов, создаваемых через фабрики. И особенно, если при этом между ними есть какие-то связи. Т. е. по сути это централизованная фабрика. Т. е. в простых случаях достаточно обычных фабрик (per class). Далее - передавать указатель на IServiceLocator я бы не стал. Уж саму фабрику точно делал бы синглтоном, ну или просто статик поля/методы. Смысл абстрагировать абстракцию?
Во-первых, в плане life scope в случае навороченных DI-контейнеров всегда есть опция синглетона. Наличие одного (и только одного) экземпляра класса в системе - не есть плохо. Плохо то, что мы жёстко зависимы от этого. Во-вторых - будут ли проблемы с потоками, это зависит от конкретного кода объекта. И в большинстве случаев наиболее удачным решением будет просто использование TLS а не локи (по крайней мере уровень паралелизма будет гораздо выше). Любой уважающий себя компилятор это поддерживает, одним дефайном решается необходимость привязки к конкретному компилеру. |
||||||
|
|||||||
Earnest |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 5962 Регистрация: 17.6.2005 Где: Рязань Репутация: 53 Всего: 183 |
TLS - это хороший выход только если каждый поток должен иметь свой экземпляр. А это вроде бы не так. Если система не очень сложная, может, не стоит парится с thread-safe синглетона. Т.е. доступ к данным действительно нужно синхронизировать. А "double checked locking паттерн" реально нужен только, если возможны накладки при создании или уничтожении. -------------------- ... |
|||
|
||||
Леопольд |
|
|||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 943 Регистрация: 17.6.2009 Репутация: 10 Всего: 13 |
Это сообщение отредактировал(а) Леопольд - 13.8.2010, 10:20 -------------------- вопросов больше чем ответов |
|||
|
||||
Леопольд |
|
|||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 943 Регистрация: 17.6.2009 Репутация: 10 Всего: 13 |
-------------------- вопросов больше чем ответов |
|||
|
||||
Леопольд |
|
||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 943 Регистрация: 17.6.2009 Репутация: 10 Всего: 13 |
Не пойму, зачем здесь атомарные операции? Любой поток будет залочен до тех пор, пока объект не будет полностью сконструирован. Это сообщение отредактировал(а) Леопольд - 13.8.2010, 11:34 -------------------- вопросов больше чем ответов |
||||
|
|||||
SABROG |
|
||||||||||
![]() Hacker ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2481 Регистрация: 18.9.2006 Репутация: 4 Всего: 91 |
QtCreator для кода:
Использует разновидность синглтона Мейерса. Только если в оригинале он не потоко-безопасен, то у них подход хитрее. Предполагается, что любое приложение изначально создается как single threaded, поэтому пока мы не наплодили других потоков предварительно происходит инициализация всех "синглтонов", например где-нибудь в main(). Сам вариант приблизительно выглядит так:
Недостатки такого подхода очевидны, но я пока склоняюсь к этому варианту, как наиболее простому и хоть частично потоко-безопасном по сравнению с таким вариантом, который без синхронизации потоков вообще не безопасен:
Чтобы отвязаться от класса ServiceLocator, впихнуть тесты, при желании подсунуть "заглушку". Предположим я хочу научиться делать автомобили. Специалисты отправляют меня сначала научиться делать велосипеды. Когда я научусь их делать, то настанет очередь чего-то более сложного. То есть на данном этапе мне важно не быстрей что-либо написать, а понять как делать это правильно в будущем, когда такая необходимость настанет.
В случае заранее проинициализированного указателя проблем атомарного чтения самого указателя возникнуть не должно (по крайней мере на AI-32), а работу с данными на которые он указывает нужно синхронизировать, благо в Qt достаточно поток-безопасных классов и Qt сама берет на себя эту рутину. Тут конечно остается проблема с "висячим" объектом синглтона, который может быть даже и не используется нигде в коде (но это редкий случай, нафига его тогда вообще создавать).
Во первых мутекс блокирует работу всех остальных потоков, которые пытаются получить доступ к синглтону, а могли бы делать полезную работу. Если дальше копать, то там нужен второй мутекс, а потом в итоге и он не спасает, погугли на "Double Checked Locking Broken". Как я уже упомянул нужен "безопасный" вариант шаблона Double Checked Locking, который реализован в функции boost::callonce(), вот пример нормального потоко-безопасного синглтона: http://www.boostcookbook.com/Recipe:/1235044 Но это мне не подходит, так как я пока не хочу тянуть boost, да еще и ради антипаттерна. Это сообщение отредактировал(а) SABROG - 13.8.2010, 11:49 |
||||||||||
|
|||||||||||
Любитель |
|
||||
Программист-романтик ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 3645 Регистрация: 21.5.2005 Где: Воронеж Репутация: 24 Всего: 92 |
А, так ты ведёшь речь именно про создание инстанса. А я то думал про "небезпоасные" его методы. Если ты уверен в безопасности методов - то очевидно, что тут и обычные объекты синхронизации (т. е. обычная блокировка) подойдут:
Ну.. А создание сервис локатора кто-то ж будет делать? Потом будем это создание абстрагировать и т. д. Я не очень вижу в этом смысл. Заглушку на классы, занимающиеся "функционалом" - ок. Тесты - ок. Но зачем сервис-локатор абстрагировать? Единственный случай, который можно придумать - это желание использовать для него какой-т фреймворк и возможность переключение между разными фреймворками (не в процессе, а в перспективе проекта). Но.. это только если речь про достаточно нетривиальную реализацию. |
||||
|
|||||
SABROG |
|
|||
![]() Hacker ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2481 Регистрация: 18.9.2006 Репутация: 4 Всего: 91 |
Так это и есть тот самый "сломанный" вариант Double Checked Locking паттерна. Зачем-то разработчики Microsoft его используют (IServiceLocator + IServiceProvider). Видимо, чтобы конечный пользователь мог подставлять собственные фабрики. К тому же опять идет жесткая привязка к конкретной реализации: ![]() |
|||
|
||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |