Russian Qt Forum
Май 10, 2024, 00:56 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 2 3 [4]   Вниз
  Печать  
Автор Тема: Обсуждение архитектуры приложения. Пример с фабричным методом  (Прочитано 31726 раз)
alexis031182
Гость
« Ответ #45 : Июнь 19, 2012, 14:48 »

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

"Начинается" фабрика с объявления абстрактного класса конструктора объектов - AbstractObjectConstructor. Хотя данный класс сам по себе ничего и не делает, тем не менее он устанавливает одно из правил формирования фабрикой новых объектов.

Следующим по списку идёт ObjectConstructor. Поскольку он является наследником AbstractObjectConstructor, ему уже предопределён тип объекта, который он должен вернуть в методе create(). Внутри же этого метода производится создание не базового класса, а того, что затребован из вне.

И наконец - ObjectFactory, суть сама фабрика объектов. Использовать просто:
Код
C++ (Qt)
class MyObject {};
 
class MyObject_Child1 : public MyObject {};
 
class MyObject_Child2 : public MyObject {};
 
ObjectFactory<QString, MyObject> factory;
//При этом никто не мешает написать, например, так: ObjectFactory<int, MyObject> factory;
 
factory.registerClass<MyObject_Child1>("my object 1");
factory.registerClass<MyObject_Child2>("my object 2");
 
MyObject_Child1 *child1 = qobject_cast<MyObject_Child1*>(factory.create("my object 1"));
MyObject_Child2 *child2 = qobject_cast<MyObject_Child2*>(factory.create("my object 2"));
 
« Последнее редактирование: Июнь 19, 2012, 15:12 от alexis031182 » Записан
alexis031182
Гость
« Ответ #46 : Июнь 26, 2012, 15:36 »

В продолжение темы о фабрике на шаблонах. Слегка видоизменил первоначальный вариант, интегрировав логику пула объектов в общий функционал фабрики. Городить огород с отдельным классом не стал, уж больно коряво тот вписывался в фабрику. Функции регистрации и дерегистрации классов не thread-safe, т.к. их вызов производится, соответственно, в начале и в конце работы приложения одним и тем же потоком. Впрочем, по желанию мьютексы добавить не проблема. В тоже время функции пула acquire() и release() мьютексами "прикрыты". Для хранения зарезервированных объектов используется QLinkedList, т.к. любая из вызываемых пулом операций выполнится с ним за O(1), что конечно важно.

Разделение функционала фабрики на классы AWebFactory и AWebConstructor позволяет использовать следующую особенность:
Код
C++ (Qt)
class AWebSocketFactory : public AWebFactory<QLatin1String, AWebSocket>
{
public:
   //! Конструктор.
   AWebSocketFactory() {
       registerClass<AWebEpollSocket>(QLatin1String("AWebEpollSocket"));
       _constructor = constructor(QLatin1String("AWebEpollSocket"));
   }
 
   //! Деструктор.
   virtual ~AWebSocketFactory() {}
 
   //! Функция создания объекта класса.
   AWebSocket *create() const {return _constructor->create();}
 
   //! Функция возврата ранее зарезервированного, либо нового объекта класса.
   AWebSocket *acquire() {return _constructor->acquire();}
 
   //! Функция установки объекта класса в резерв.
   void release(AWebSocket *object) {_constructor->release(object);}
 
private:
   AbstractConstructor *_constructor;
 
};
В данном примере, у нас имеется лишь один класс сокетов AWebEpollSocket, унаследованный от AWebSocket. Таким образом, дабы напрямую использовать пул, минуя при этом фабрику, мы переопределяем соответствующие функции. При этом фабричный функционал конечно же остаётся доступен. Если же нам потребуется добавить новые типы сокетов, и какой-либо из них будет использоваться чаще, нежели другие, то в этом случае возможна реализация функции, например selectConstructor(), которая по указанному идентификатору/ключу класса будет устанавливать указатель _constructor на необходимый для текущей работы класс конструктора посредством вызова AWebFactory::constructor(const Key &key).
Записан
DmitryM
Гость
« Ответ #47 : Июнь 26, 2012, 16:29 »

Я думаю о том, чтобы вообще исключить процесс создания объектов классов из фабрики, а забирать уже готовые, созданные ранее и инициализированные параметрами по умолчанию.
Собственно этом занимается паттерн Prototype.
Записан
DmitryM
Гость
« Ответ #48 : Июнь 26, 2012, 16:34 »

а о таких ягодках как QString::split (столь любимых молодежью) неудобно и говорить. Возможно нужна разумная примитивизация (в духе С) 
Используй паттерн Builder, для создания строк( как в Java и C#).
Записан
alexis031182
Гость
« Ответ #49 : Июнь 26, 2012, 16:46 »

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

Впрочем, на классах сокетов какого-либо заметного ускорения не ощущается. Посмотрю ещё, что будет с классами запросов и заголовков. У них "конвейер" в разы быстрее нужен.
Записан
alexis031182
Гость
« Ответ #50 : Июнь 26, 2012, 16:50 »

Используй паттерн Builder, для создания строк( как в Java и C#).
Возможно. Пока просто у меня использование строк ограничивается лишь сравнением. QLatin1String нормально работает в этом отношении. С char'ами может быть потом что-нибудь подумаю.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #51 : Июнь 26, 2012, 18:46 »

Пока мне нечего ответить по существу - любые конструкции хороши если они делают то что надо. Неясно как выбираете объект из пула.
Для хранения зарезервированных объектов используется QLinkedList, т.к. любая из вызываемых пулом операций выполнится с ним за O(1), что конечно важно.
А как это будет работать? Стандартная техника переносить из одного списка в другой.
Записан
alexis031182
Гость
« Ответ #52 : Июнь 26, 2012, 20:09 »

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

Объект из пула выбирается вызовом функции AWebConstructor::acquire(). Если в пуле нет объектов, она просто создаст новый.

А как это будет работать? Стандартная техника переносить из одного списка в другой.
Я отказался от такого подхода. Второй список объектов, которые были выданы пулом, в пуле не создаётся. Другими словами, пул после выдачи объекта ничего не знает о судьбе этого объекта. Но в любой момент, когда мы попользовались объектом, мы можем вернуть его в пул при помощи AWebConstructor::release().

Я подумал, что хранить пулом два списка в общем-то излишне. Во-первых, затраты на перемещение указателей из списка зарезервированных в список используемых и обратно. Во-вторых, объект, будучи изъят из пула, может быть удалён. Это плохо, пришлось бы задействовать "умные" указатели. В-третьих, пул независим от места создания объекта. Объект может быть создан где угодно и не обязательно в пуле, но при этом в пул может быть без проблем помещён.

Вполне вероятно, что я отошёл в сторону от традиционной реализации паттерна пула объектов, но мне показался такой подход очень удобным.
Записан
alexis031182
Гость
« Ответ #53 : Июнь 27, 2012, 18:27 »

Продолжаем развивать фабрику на шаблонах (ёлки, уже можно версии вводить Улыбающийся )... На этот раз у меня возникла ситуация, связанная с необходимостью создания объектов классов, унаследованных от общего предка, но имеющихся в единичном экземпляре. Например:
Код
C++ (Qt)
class Base {};
 
class Child1: public Base {};
class Child2: public Base {};
...
class ChildN: public Base {};
 
Обязательным условием к этому является то, что объекты этих классов должны создаваться через ключ/идентификатор. Другими словами через фабрику. В приложении необходимо использовать лишь указатели на эти объекты, никакой необходимости в их (объектов) клонировании нет.

Предыдущая версия фабрики с пулом объектов мне не подходила, поскольку там на каждый вызов acquire() фабрика либо создавала новый объект, либо возвращала один из существующих, коих во внутреннем её контейнере может быть превеликое множество. В новой же задаче формулировалось требование, что каждый из набора Child1, ..., ChildN необходимо создавать лишь в единственном экземпляре.

Поскольку обе задачи (фабрика с пулом объектов и фабрика единичных объектов) входят у меня в один проект, я решил совместить весь необходимый функционал в одном наборе классов. Изменения:
  • класс AWebConstructor переименован в AWebPoolConstructor (не смейтесь, пишу об этом, чтобы путаницы не было Улыбающийся );
  • добавлен класс AWebObjectConstructor, который и реализует функционал фабрики единичных объектов;
  • в классе AWebFactory, в функцию registerClass() добавлен параметр, определяющий, по какому алгоритму из двух имеющихся вариантов фабрика будет функционировать (то есть пул или единичный экземпляр).

Обратите внимание, что установка типа алгоритма работы фабрики производится именно в функции AWebFactory::registerClass(). Это позволяет комбинировать варианты действий для разных наследников одного предка одной и той же фабрики. Не исключаю, что подобное вряд ли будет широко востребовано, но тем не менее имеет право на жизнь.

Ну и наконец пару слов о новом AWebObjectConstructor. Тут всё просто: используется "умный" указатель. Если объект будет удалён из вне, то по новому запросу он будет пересоздан вызовом acquire(). Эта же функция возвратит указатель на актуальный объект. Функция release() позволяет заместить имеющийся объект новым из вне.
« Последнее редактирование: Июнь 27, 2012, 18:29 от alexis031182 » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #54 : Июнь 27, 2012, 20:38 »

Слишком вольное выделение/создание объектов мне не нравится (что конечно не означает "неправильно"). Кто-то "на стороне" может создать - и вернуть в фабрику. Проходной двор. Мне кажется надо проводить более последовательную идеологию

- никто не может ни создать ни удалить объект минуя фабрику. Пользователь получает не сами объекты а stub'ы, удаление которых ведет к освобождению объекта - но не удалению.

- все созданные объекты известны фабрике в любой момент. Важно иметь возможность влиять на все имеющиеся обработчики

Вот напр возникла потребность возвращать уже имеющийся объект независимо от его занятости. Если фабрика контролирует все, реализация выглядит примерно так
Код
C++ (Qt)
Object * Factory::GetFreeObject( const char * name ); // создаем новый если нет свободных
Object * Factory::GetAnyObject( const char * name ); // возвращаем имеющийся свободен или нет
 
Без всякого накручивания конструкторов, типов и.т.п.
 
Записан
alexis031182
Гость
« Ответ #55 : Июнь 27, 2012, 22:10 »

Слишком вольное выделение/создание объектов мне не нравится (что конечно не означает "неправильно"). Кто-то "на стороне" может создать - и вернуть в фабрику. Проходной двор. Мне кажется надо проводить более последовательную идеологию
Это Вы про пул объектов, правильно? Ну или про фабрику как у меня, совмещённую с пулом.

Для того чтобы пул имел возможность отметить "занятые" объекты, их либо придётся перемещать во вторичный список, либо использовать специальный интерфейс (абстрактный класс), от которого эти объекты должны быть унаследованы. Второй вариант конечно хуже всего. А первый - традиционный пул. Другие варианты мне неизвестны.

В моём пуле уязвимым местом (может есть ещё, но для поиска таковых и форум Улыбающийся ), как Вы справедливо заметили, является помещение объекта обратно в пул. В конце концов можно просто нулевой указатель вставить. Или поместить объект в пул, а затем удалить его по указателю из вне. Над этим можно подумать, либо просто иметь в виду.

- никто не может ни создать ни удалить объект минуя фабрику. Пользователь получает не сами объекты а stub'ы, удаление которых ведет к освобождению объекта - но не удалению.
Можно пример?

- все созданные объекты известны фабрике в любой момент. Важно иметь возможность влиять на все имеющиеся обработчики

Вот напр возникла потребность возвращать уже имеющийся объект независимо от его занятости. Если фабрика контролирует все, реализация выглядит примерно так
Код
C++ (Qt)
Object * Factory::GetFreeObject( const char * name ); // создаем новый если нет свободных
Object * Factory::GetAnyObject( const char * name ); // возвращаем имеющийся свободен или нет
 
Без всякого накручивания конструкторов, типов и.т.п.
По сути, это весьма небезопасно. Мало ли чем объект может быть занят. В каком треде он выполняется и т.п.

Накручивание конструкторов и типов сделано не просто так (отмечу также факт, что на скорости работы приложения применение шаблонов никак не сказывается). Вот в Вашем примере Вы возвращаете некий Object. Хорошо если все объекты, которые Вы "пропускаете" через фабрику унаследованы от него. А если имеют разных предков, которые в свою очередь к Object не имеют никакого отношения? Что тогда, множественное наследование? Например, мне у себя в проекте пришлось отказаться от использования QObject по той лишь причине, что он весьма тяжёл. Но это наверное плата в Qt за универсальность. Где-то даже видел в кьютишном доке объяснение, почему они не используют шаблоны, а пошли по пути сигнал/слот.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #56 : Июнь 28, 2012, 11:45 »

По сути, это весьма небезопасно. Мало ли чем объект может быть занят. В каком треде он выполняется и т.п.
Это столь же опасно/безопасно как и любая реализация возвращающая единичный объект

Накручивание конструкторов и типов сделано не просто так (отмечу также факт, что на скорости работы приложения применение шаблонов никак не сказывается). Вот в Вашем примере Вы возвращаете некий Object. Хорошо если все объекты, которые Вы "пропускаете" через фабрику унаследованы от него. А если имеют разных предков, которые в свою очередь к Object не имеют никакого отношения? Что тогда, множественное наследование?
"Некий Object" может совсем не быть базовым для классов создаваемых фабрикой. Это класс оболочки (инвентарь). Обычно он делегирует действия к экземпляру фабричного класса. Сами эти классы могут быть слабосвязны или вообще несвязны между собой. Поэтому для каждого выписывается класс оболочки - вот он уже имеет общую базу (Object) и виртуалы.    

Без этой оболочки дело сводится к пулу(ам). Да, можете создавать по имени, резервировать и.т.п. Но "прогнать" через это какое-то действия (т.е. последовательно пробовать с разными классами фабрики) не получается - нет общих виртуалов  

Edit: вот Вы пишете
Код
C++ (Qt)
template<typename Key, class Base>
class AWebFactory
{
   //! Функция создания объекта класса.
   Base *create(const Key &key) const {return _classes.value(key)->create();}
 
Тогда как уложить в эту фабрику объекты не-наследники Base?
« Последнее редактирование: Июнь 28, 2012, 11:56 от Igors » Записан
alexis031182
Гость
« Ответ #57 : Июнь 28, 2012, 12:28 »

Это столь же опасно/безопасно как и любая реализация возвращающая единичный объект
В моём проекте мне нужны единичные константные объекты. Они не будет меняться, будет лишь куча мест, которые по указателям будут на них ссылаться. Я подумываю над тем, как оптимально вписать константность в AWebObjectConstructor. Если же предполагать, что объекты могут меняться, то конечно с безопасностью, при их (объектов) неаккуратном использовании, могут возникнуть проблемы.

"Некий Object" может совсем не быть базовым для классов создаваемых фабрикой. Это класс оболочки (инвентарь). Обычно он делегирует действия к экземпляру фабричного класса. Сами эти классы могут быть слабосвязны или вообще несвязны между собой. Поэтому для каждого выписывается класс оболочки - вот он уже имеет общую базу (Object) и виртуалы.
Я понял. Это по сути мои классы конструкторов (сам AWebAbstractConstructor и его наследники). То есть в Вашем варианте фабрика должна возвращать именно их? Это будет потеря индивидуальности интерфейса доступа к объектам. Да, можно возвращать различные интерфейсы по идентификаторам классов, но потребует в свою очередь "ручного" приведения их типов (ведь абсолютно все возможные варианты действий через виртуальные функции одного предка не опишешь). В моём варианте, корректность приведения типов отслеживается ещё на уровне компиляции, а унифицированного интерфейса действий вполне достаточно.

Без этой оболочки дело сводится к пулу(ам). Да, можете создавать по имени, резервировать и.т.п. Но "прогнать" через это какое-то действия (т.е. последовательно пробовать с разными классами фабрики) не получается - нет общих виртуалов 
Здесь не очень понятно.
Записан
alexis031182
Гость
« Ответ #58 : Июнь 28, 2012, 12:30 »

Edit: вот Вы пишете
Код
C++ (Qt)
template<typename Key, class Base>
class AWebFactory
{
   //! Функция создания объекта класса.
   Base *create(const Key &key) const {return _classes.value(key)->create();}
 
Тогда как уложить в эту фабрику объекты не-наследники Base?
Никак, это фабрика наследников. Если нужно складывать всё подряд, то должна быть другая реализация.
Записан
alexis031182
Гость
« Ответ #59 : Июнь 29, 2012, 15:31 »

...
В моём пуле уязвимым местом (может есть ещё, но для поиска таковых и форум Улыбающийся ), как Вы справедливо заметили, является помещение объекта обратно в пул. В конце концов можно просто нулевой указатель вставить. Или поместить объект в пул, а затем удалить его по указателю из вне. Над этим можно подумать, либо просто иметь в виду.
...
QScopedPointer решает проблему с удалением объекта из вне.
Записан
Страниц: 1 2 3 [4]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.191 секунд. Запросов: 22.