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

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

Страниц: [1]   Вниз
  Печать  
Автор Тема: Qt Design Patterns Extension  (Прочитано 10087 раз)
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« : Январь 29, 2013, 22:18 »

Библиотека "Qt Design Patterns Extension" предоставляет расширенную реализацию шаблонов проектирования для Qt.

Посвящается тем, кому необходимо в памяти строить деревья из объектов разных типов, кто хочет иметь удобный доступ к его элементам, и кому надоели при этом всякие dynamic/static_cast-ы.

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

Возможно эта библиотека еще кому-нибудь окажется полезной. Будут вопросы - спрашивайте Улыбающийся.
Записан

Пока сам не сделаешь...
_OLEGator_
Гость
« Ответ #1 : Январь 29, 2013, 22:37 »

В чем суть библиотеки, она только для создания дерева из разных объектов?
Чтобы не использовать касты можно юзать метаобъектную модель, тогда вообще приводить ничего не надо...
« Последнее редактирование: Январь 29, 2013, 22:39 от _OLEGator_ » Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #2 : Январь 29, 2013, 23:29 »

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

Кроме того, в базовые классы композитного контейнера добавлены возможности шаблона проектирования Visitor, которые позволяют добавлять "якобы виртуальные" методы к уже существующим классам без их модификации. Пример этого можно увидеть в классах XMLContentWriter, XMLContentReader. Там, кстати, и используются возможности метаобъектной модели. И в то же время, именно из-за нее пример получился не совсем наглядным Улыбающийся. Надо будет его исправить.

Записан

Пока сам не сделаешь...
_OLEGator_
Гость
« Ответ #3 : Январь 30, 2013, 09:25 »

А требования какие к классам, которые могут быть обработаны контейнером, унаследование от общего абстрактного/базового класса?
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #4 : Январь 30, 2013, 12:09 »

Да, произвольный объект в контейнер вставить нельзя, для создания иерархии своих классов нужен определенный предок. Для листовых элементов это CompositeItem, для контейнеров - CompositeContainer. Например, AbstractSheet <- BookSheet, AbstractSheetCollection <- BookSheetCollection. Помимо общего предка, необходимо добавлять некоторые типы/методы, для контейнера минимум как в HomoCompositeContainer. Хотел уйти это этой копи-пасты, но, похоже, никуда от нее не денешься, можно только постараться свести ее к унифицированному виду.

Кроме последовательной индексации элементов в контейнере можно еще использовать ассоциативную. Так сделано в классах AbstractStringCatalog <- GenreCatalog, чтобы к элементу обращаться, например, таким способом:
Код:
BookShelf *bookShelf = catalog.item("Fantasy");

Выше были описаны гомогенные контейнеры, в которых содержатся элементы одинакового типа. Но иногда возникает потребность хранить в одном контейнере элементы разного типа. В классе магазина BookShop хранятся разные полки: BookShelf, NewspaperShelf и MagazineShelf. Обращаться к ним можно с помощью таких конструкций:
Код:
BookShop::Adapter aShop(bookShop());

BookShelf::Adapter aBookShelf = aShop.item<BookShop::Shelf::Book>()->adapter();
NewspaperShelf *pNewspaperShelf = aShop.item<BookShop::Shelf::Newspaper>();
auto aMagazineShelf = aShop.item<BookShop::Shelf::Magazine>()->adapter();
Может не самый удачный способ, но лучше я пока не придумал Улыбающийся.
Записан

Пока сам не сделаешь...
panAlexey
Гипер активный житель
*****
Offline Offline

Сообщений: 864

Акцио ЗАРПЛАТА!!!!! :(


Просмотр профиля
« Ответ #5 : Январь 30, 2013, 12:18 »

Посмотрим.
Есть пара задач под это.
Записан

Win Xp SP-2, Qt4.3.4/MinGW. http://trdm.1gb.ru/
Akon
Гость
« Ответ #6 : Январь 31, 2013, 01:31 »

Цитировать
Может не самый удачный способ, но лучше я пока не придумал
Есть вариант с CRTP оберткой. Например, пусть Item - базовый класс для листа, имеющий обратный указатель на содержащий его контейнер - метод сontainer(). Нам требуется, чтобы ConcreateItem выдавал в данном методе ConcreateContainer*:
Код:
// Item

class Item
{
public:
Container* container() { return container_; }
const Container* container() const { return const_cast<Item*>(this)->container(); }

private:
Container* container_;

...
};

// TheItem

/// Thing class template to generate more closured container related interface
template <typename ContainerT, typename BaseT = Item>
class TheItem : public BaseT
{
typedef BaseT Base;

public:
ContainerT* container() { return static_cast<ContainerT*>(Base::container()); }
const ContainerT* container() const { return static_cast<const ContainerT*>(Base::container()); }

...
};

Использование непринужденно:
Код:
// ConcreateItem

class ConcreateItem : public TheItem<ConcreateContainer, Item>
{
...
};

...

ConcreateItem item;
item.container();  // returns ConcreateContainer*
К недостаткам следует отнести замусоливание дерева классов прокладками TheItem/TheContainer.

Без компоновщика не обходится практически ни одна хоть сколько-нибудь серьезная программа, поэтому эта тема интересная. Бегло, но с удовольствием посмотрел вашу реализацию, спасибо. Мои наблюдения/замечания:
- В случае большого количества элементов наследование от QObject для базовых типов может быть нецелесообразно.
- Мне практически всегда приходилось отслеживать добавление/удаление элементов из контейнеров, например, элемент меняет контейнер. Поэтому в методах insert/remove должны вызываться полиморфные защищенные методы itemInserted()/ItemRemoved(), которые по-умолчанию выбрасывают соответствующие сигналы. В более общем случае, должно быть двухфазное оповещение на события, например: itemInserting(item) - до вставки и itemInserted(item) - после, т.к. это может требоваться другим компонентам, например, QAbstractItemModel.
- Для С++ компоновщик менее универсален в сравнении с языками, допускающими полиморфные вызовы из конструкторов/деструкторов. Поясню: часто логикой требуется, что элемент всегда имеет контейнер. В этом случае а) контейнер передается в конструктор элемента и происходит вставка - нельзя, если insert() имеет полиморфные вызовы; б) контейнер (или какой-нибудь посредник) создает элемент через фабрику и вставляет его - все ок, но появляется лишний класс - фабрика; в) говнокод - когда клиент должен создать объект без контейнера, но перед использованием обязательно вставить объект в контейнер.
- Итераторы для компоновщика элегантно реализуются через CRTP. Каждое хранилище (store) имеет свой STL-compatible итератор (нешаблонный). Под ConcreateItem делается CRTP шаблон-обертка TheIterator, которая выполняет даункастинг.
- В общем случае компоновщик можно ортогонализовать (выразился то как  Улыбающийся но по делу) по различным стратегиям. Стратегия хранения у вас уже имеется. Также напрашиваются стратегии по возвращаемым типам элементов (raw pointer, shared_pointer и т.п.).
- Операции сортировки и фильтрации не стоит реализовывать на уровне компоновщика, тем самым загромождая его. Лучше вынести это в отдельный слой с проксированием, подобно QAbstractItemModel -> QAbstractProxyModel.
- Если уж используете const propagation, то делайте это везде (я видел const метод findItem, возвращающий non-const pointer).
- Вы используете copy ctor() & operator=() для типов сущностей (identity type), коими являются все QObject'ы (т.е. ваши CompositeItem и т.д.), тем самым нарушая наказ родителя QObject: Q_DISABLE_COPY(). Несмотря на техническую корректность ваших методов, лучше использовать для этих целей методы clone() и assign() соответственно, а copy ctor() & operator=() оставить для value type.
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #7 : Январь 31, 2013, 11:59 »

Есть вариант с CRTP оберткой.
Спасибо за вариант, посмотрю, можно будет ли его где-нибудь применить. Хотя одна вещь уже останавливает: использование шаблонного класса в качестве предка для другого.
Код:
class ConcreateItem : public TheItem<ConcreateContainer, Item>
Многие инструменты не любят шаблоны. Всякие автодополнения в редакторах и генераторы документации, например Doxygen. В этом случае он не сможет построить нормальный граф наследования классов, каким он, по-идее, должен быть. Я такие варианты использования шаблонов пробовал, и теперь, по мере возможности, стараюсь их избегать. Похоже нормально справиться с шаблонами под силу только компилятору  Веселый.

Цитировать
В случае большого количества элементов наследование от QObject для базовых типов может быть нецелесообразно.
Это возможно для некоторого класса задач. Но я писал эту библиотеку специально для использования в Qt, а не общую реализацию для любых классов. Общую реализацию быстрее, красивше и лучше, наверняка, можно сделать с помощью Boost, там вроде достаточно возможностей для этого. Я такой вариант рассматривал, но решил не привязываться к Boost'у, да и стиль классов Qt мне нравится намного больше. А если в Qt не унаследоваться от QObject, то могут закидать камнями, типа: "И как мне теперь сигналы подключать? А metaObject() где?". В общем, я не рискнул Улыбающийся.

Цитировать
Мне практически всегда приходилось отслеживать добавление/удаление элементов из контейнеров, например, элемент меняет контейнер.
Эту функциональность надо будет обдумать, насколько необходима и востребована она будет. С одной стороны вещь, безусловно, полезная, с другой стороны, не хочется загромождать базовые классы. Как-то баланс надо в этом найти.

Цитировать
Для С++ компоновщик менее универсален в сравнении с языками, допускающими полиморфные вызовы из конструкторов/деструкторов.
С этим тоже сталкивался, ощущения не из приятных - заставляет придумывать костыли, чего стараюсь избегать.

Цитировать
Итераторы для компоновщика элегантно реализуются через CRTP. Каждое хранилище (store) имеет свой STL-compatible итератор (нешаблонный). Под ConcreateItem делается CRTP шаблон-обертка TheIterator, которая выполняет даункастинг.
С итераторами я там столько наворотил, что сейчас их лучше не трогать Веселый.

Цитировать
В общем случае компоновщик можно ортогонализовать (выразился то как   но по делу) по различным стратегиям. Стратегия хранения у вас уже имеется. Также напрашиваются стратегии по возвращаемым типам элементов (raw pointer, shared_pointer и т.п.).
Хотел сделать такую возможность, и даже пробовал. Очень тяжко это шло, много проблем возникало, поэтому пока остановился на QPointer в хранилище.

Цитировать
Операции сортировки и фильтрации не стоит реализовывать на уровне компоновщика, тем самым загромождая его. Лучше вынести это в отдельный слой с проксированием, подобно QAbstractItemModel -> QAbstractProxyModel.
С этим согласен, каждый должен заниматься своим делом: контейнер хранить, сортировщик сортировать и т.п.

Цитировать
Если уж используете const propagation, то делайте это везде (я видел const метод findItem, возвращающий non-const pointer).
Тут возможна моя недоработка. По-хорошему, надо провести ревизию кода на предмет этого. Наверняка там много где еще можно const добавить.

Цитировать
Вы используете copy ctor() & operator=() для типов сущностей (identity type), коими являются все QObject'ы (т.е. ваши CompositeItem и т.д.), тем самым нарушая наказ родителя QObject: Q_DISABLE_COPY(). Несмотря на техническую корректность ваших методов, лучше использовать для этих целей методы clone() и assign() соответственно, а copy ctor() & operator=() оставить для value type.
Это все правильно, и надо еще хорошо подумать, как лучше клонирование организовать. Такой способ я ввел на самом раннем этапе, "чтоб был", потому что реальную его работу можно будет проверить на более позднем этапе, когда будет много вариантов деревьев для тестирования. Если честно, клонированием больших деревьев еще не занимался. Вопрос непростой, требует размышлений и тестирования. К особенностям некопирования QObject добавляются особенности компоновщика. Например, в одном контейнере может находиться ссылка на элемент в другом контейнере, и при ее копировании могут возникнуть сложности.

В общем, большое спасибо за полезные замечания Улыбающийся. Очень важен взгляд со стороны.
Записан

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #8 : Январь 31, 2013, 13:59 »

Интересно, но въехать и понять о чем речь трудновато Улыбающийся Обычная техника - контейнер указателей базового класса. Да, как правило нужны итераторы "по каждому из типов", ну я напр реализовывал в процедурном стиле
(FirstTypedObject, NextTypedObject) и особых трудностей не испытывал.

А вот капитальный геморрой начинается если объект хранит указатели на др объекты. Удаляя объект нужно как-то вычеркнуть его из хранящих. Но это еще цветочки - есть ведь Undo  Плачущий   Возможно это др тема
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #9 : Январь 31, 2013, 14:55 »

Интересно, но въехать и понять о чем речь трудновато Улыбающийся Обычная техника - контейнер указателей базового класса. Да, как правило нужны итераторы "по каждому из типов", ну я напр реализовывал в процедурном стиле (FirstTypedObject, NextTypedObject) и особых трудностей не испытывал.

Да, на первый взгляд выглядит заморочено, но если разобраться, то не все так страшно Улыбающийся. Базовые классы CompositeContainer и CompositeItem довольно простые, все преобразования типов ложатся на плечи CompositeAdapter. Он и доступ к методам контейнера с конкретным типом элемента предоставит, и итератор подходящий создаст. Дополнительная прослойка, конечно, но, возможно, это не самое худшее из зол Улыбающийся.
Записан

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Январь 31, 2013, 15:32 »

Да, на первый взгляд выглядит заморочено,
То да Улыбающийся Хорошо, а вот такая проблемка: общее число объектов приличное - напр неск тысяч. В то же время число "нужного" типа может быть с гулькин нос, а то и вообще ни одного. И что, итератор все равно должен промолотить все тысячи? 
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #11 : Январь 31, 2013, 17:06 »

Хорошо, а вот такая проблемка: общее число объектов приличное - напр неск тысяч. В то же время число "нужного" типа может быть с гулькин нос, а то и вообще ни одного. И что, итератор все равно должен промолотить все тысячи?
Тут мы, наверное, по разному понимаем, что в контейнере может находится Улыбающийся. В моем варианте в одном контейнере или много элементов одного типа (хоть несколько тысяч), или достаточно малое количество разных, причем заранее известно, в какой позиции какой тип находится. Можно, конечно, и тысячу разнотипных объектов в один контейнер засунуть, но это архитектурно другая задача будет. Это больше похоже на метод QObject::findChildren, который возвращает список объектов определенного типа, по которому потом можно пройти итератором. Правда пока он этот список создаст, он все тысячи объектов и промолотит Улыбающийся.
Записан

Пока сам не сделаешь...
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


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