Интересный вопрос возник, все никак не родить решения...
Есть иерархия классов (чисто в теории)
class Shape //базовый
{
virtual void Move(double dt) = 0;
virtual void Draw(HDC dc) const = 0;
virtual ~Shape();
// другие методы
};
class SomeShape : public Shape
{
void Move(double dt) { /*...*/ }
void Draw(HDC dc) { /*...*/ }
};
class OtherShape : public Shape
{
void Move(double dt) { /*...*/ }
void Draw(HDC dc) { /*...*/ }
};
Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации Draw наружу. Также хочется оставить за собой возможность абстрагироваться от HDC и поддерживать отрисовку не только на нем, но и, скажем, на поверхностях DirectX и в GDI+.
Думается, что реализации Draw должны располагаться в отдельных иерархиях, но быть в то же время привязанными к основной иерархии.
Пример надуманный, но зато довольно наглядный. Чувствую, паттерны должны помочь; но какой/какие из них?
Здравствуйте, Дядюшка Че, Вы писали:
ДЧ>Также хочется оставить за собой возможность абстрагироваться от HDC и поддерживать отрисовку не только на нем, но и, скажем, на поверхностях DirectX и в GDI+.
классика двойной диспетчеризации — см. паттерн visitor
L>>Как вариант — иерархия renderer-ов и их использование через паттерн decorator. LM>Декоратор тут явно не при чем
Это почему же?
В GoF был довольно похожий пример про "просто конрол" и "контрол с рамочкой".
Если Shape-ы слабо связаны между собой по иерархии, но отрисовываются похожим образом, или если отрисовка может меняться на лету в зависимости от состояния Shape — может подойти.
ИМХО, конечно.
Здравствуйте, Left2, Вы писали:
L>>>Как вариант — иерархия renderer-ов и их использование через паттерн decorator. LM>>Декоратор тут явно не при чем
L>Это почему же? L>В GoF был довольно похожий пример про "просто конрол" и "контрол с рамочкой".
Был. L>Если Shape-ы слабо связаны между собой по иерархии, но отрисовываются похожим образом, или если отрисовка может меняться на лету в зависимости от состояния Shape — может подойти. L>ИМХО, конечно.
Но тут вроде совсем не тот случай
Здравствуйте, Дядюшка Че, Вы писали:
ДЧ>Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации Draw наружу. Также хочется оставить за собой возможность абстрагироваться от HDC и поддерживать отрисовку не только на нем, но и, скажем, на поверхностях DirectX и в GDI+.
Я понял, о чем Вы спрашиваете. Ответ будет содержать 3 "пласта":
1. В простейшем случае Вы можете делегировать обязанности по рисованию объектов самим объектам. И каждый объект будет содержать функции НарисуйМеняВОкне и НарисуйМеняВКонсоли:
У такого решения есть недостаток: при добавлении нового просмотра нужно добавить соответствующую виртуальную функцию во все классы иерархии.
2. Более сложный вариант — это проанализировать операции, которые нужны для рисования объектов, и объединить все просмотры (устройства вывода) в одну иерархию классов. Например, так:
class TDrawDevice
{
public:
virtual void DrawRect(const TRect & rRect);
virtual void DrawText(const int x, const int y, const std::string & rText);
// И т.д.
};
class TWindowDevice : public TDrawDevice
{
public:
virtual void DrawRect(const TRect & rRect);
virtual void DrawText(const int x, const int y, const std::string & rText);
// И т.д.private:
TWindow & m_rWindow;
};
class TConsoleDevice : public TDrawDevice
{
public:
virtual void DrawRect(const TRect & rRect);
virtual void DrawText(const int x, const int y, const std::string & rText);
// И т.д.private:
TConsole & m_rConsole;
};
Соответственно, каждый объект будет иметь только одну функцию рисования:
3. Наконец, последний и самый абстрактный вариант — это отказаться от способа построения документа, как контейнера указателей на классы некоей иерархии. Лучше представить документ в виде базы данных, к которой можно сформировать ряд запросов. В этом случае получается, что каждый просмотр (вид) обращается к базе данных (документу) с запросом тех данных, которые ему (виду) нужны. Таким образом, получается разделение обязанностей:
1) Документ предоставляет необходимые данные по запросу (для этого у него есть соответствующий интерфейс).
2) Каждый просмотр (вид) получает нужные ему данные и отображает их нужным образом.
Есть очень серьезное предположение, что профессиональные редакторы спроектированы по Модели 3. На сложной предметной области или при возрастании количества разнородных требований обычную иерархию классов просто "разносит". Иерархия оказывается нежизнеспособной.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>3. Наконец, последний и самый абстрактный вариант — это отказаться от способа построения документа, как контейнера указателей на классы некоей иерархии. Лучше представить документ в виде базы данных, к которой можно сформировать ряд запросов. В этом случае получается, что каждый просмотр (вид) обращается к базе данных (документу) с запросом тех данных, которые ему (виду) нужны. Таким образом, получается разделение обязанностей:
КЛ>1) Документ предоставляет необходимые данные по запросу (для этого у него есть соответствующий интерфейс). КЛ>2) Каждый просмотр (вид) получает нужные ему данные и отображает их нужным образом.
КЛ>Есть очень серьезное предположение, что профессиональные редакторы спроектированы по Модели 3. На сложной предметной области или при возрастании количества разнородных требований обычную иерархию классов просто "разносит". Иерархия оказывается нежизнеспособной.
Было бы интересно увидеть развитие ваших идей до интерфейса исходя из базового примера фигур круг/квадрат и 2х устройств просмотра.
а то использование слов "база данных" наводит на нехорошие мысли.
wbr
Здравствуйте, SleepyDrago, Вы писали:
SD>Было бы интересно увидеть развитие ваших идей до интерфейса исходя из базового примера фигур круг/квадрат и 2х устройств просмотра.
Как ни странно, круг и квадрат — это не две разных фигуры, а одна. Т.е. иерархия классов в стиле:
// Базовый класс фигуры.class TFigure {...};
// Круг.class TCircle : public TFigure {...};
// Квадрат.class TQuadrate : public TFigure {...};
Здравствуйте, Alxndr, Вы писали:
A>Можно услышать обоснование "принципиальной ошибочности"?
Можно. Их несколько:
1) В Microsoft Word XP я насчитал 114 автофигур. И это не считая того, что в Word'е есть возможность добавить другие автофигуры. Вы представляете, сколько надо классов, если придерживаться правила "по классу на кажду фигуру"?
2) Фигуры нужны для того, чтобы над ними выполнять операции. Если потребуется изменять форму, например, с помощью инструмента Shape, то правило "по классу на каждую фигуру" опять-таки не сработает, т.к. в результате преобразования формы одна фигуры может легко преобразоваться в другую. Например, roundrect легко можно преобразовать и в прямоугольник, и в эллипс.
Здравствуйте, Кирилл Лебедев, Вы писали:
A>>Можно услышать обоснование "принципиальной ошибочности"?
КЛ>Можно. Их несколько:
КЛ>1) В Microsoft Word XP я насчитал 114 автофигур. И это не считая того, что в Word'е есть возможность добавить другие автофигуры. Вы представляете, сколько надо классов, если придерживаться правила "по классу на кажду фигуру"?
И что с того? Оверхед в 114 виртуальных таблиц?
И самое главное: что это доказывает?
КЛ>2) Фигуры нужны для того, чтобы над ними выполнять операции. Если потребуется изменять форму, например, с помощью инструмента Shape, то правило "по классу на каждую фигуру" опять-таки не сработает, т.к. в результате преобразования формы одна фигуры может легко преобразоваться в другую. Например, roundrect легко можно преобразовать и в прямоугольник, и в эллипс.
Ты привел конкретную задачу, в которой иерархия фигур смысла действительно не имеет.
Очевидно, "принципиальную ошибочность" подхода с иерархией фигур это никак не доказывает.
ДЧ>Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации Draw наружу. Также хочется оставить за собой возможность абстрагироваться от HDC и поддерживать отрисовку не только на нем, но и, скажем, на поверхностях DirectX и в GDI+.
Простой способ, сделать статический класс или синглетон, предоставляющий доступ либо к DirectX либо к HDC, с реализаций какких то общих фун-ий или действий ...
class SomeShape : public Shape
{
void Move(double dt) { /*...*/ }
void Draw(HDC dc) { /*...*/ }
GeneralCanvas::Instance->Draw(Direct ....);
или
GeneralCanvas::Instance->Draw(HDC ....);
или получать контекст
GeneralCanvas::Instance->getCurrent....
};
Более сложный способ, использовать factory и ServiceLocator что позволит использовать разные сочетания классов или даже одновременно ...
Здравствуйте, Alxndr, Вы писали:
A>И что с того? Оверхед в 114 виртуальных таблиц? A>И самое главное: что это доказывает?
Дело не в 114 виртуальных таблицах, а в трудоемкости создания и сопровождения 114 классов фигур. Это бессмысленно тем более, что можно обойтись всего одним (1!) классом.
A>Ты привел конкретную задачу, в которой иерархия фигур смысла действительно не имеет. A>Очевидно, "принципиальную ошибочность" подхода с иерархией фигур это никак не доказывает.
Принципиальная ошибочность доказывается очень просто: с увеличением разнообразия фигур и операций по их преобразованию "стройная" иерархия классов в стиле подхода "по классу на фигуру" просто разлетается.
Жаль, если это Вам непонятно. Возможно, Вам имеет смысл посопровождать ряд графических редакторов, написанных в стиле "по классу на фигуру" и, так сказать, воочию убедиться в ошибочности данного подхода.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Жаль, если это Вам непонятно. Возможно, Вам имеет смысл посопровождать ряд графических редакторов, написанных в стиле "по классу на фигуру" и, так сказать, воочию убедиться в ошибочности данного подхода.
Вообще-то, Кирилл хоть вы отвечаете не совсем мне, но тк я как раз сопровождаю САПР который сделан так (и многие другие тоже сделаны так )
то у меня вполне конкретный вопрос к Вам (повторю еще раз):
SD>Было бы интересно увидеть развитие ваших идей до интерфейса исходя из базового SD>примера фигур круг/квадрат и 2х устройств просмотра.
wbr
зы "круг" в моих ТЗ это многоугольник с заданным количеством точек (от 4 до 3600) в зависимости от настроения пользователя так что утверждать что
круг и квадрат это одна фигура эээ не совсем корректно
Здравствуйте, SleepyDrago, Вы писали:
SD>то у меня вполне конкретный вопрос к Вам (повторю еще раз):
После того, как мы определимся с кругом и квадратом (это существенный момент), я Вам отвечу на этот вопрос.
SD>зы "круг" в моих ТЗ это многоугольник с заданным количеством точек (от 4 до 3600) в зависимости от настроения пользователя так что утверждать что SD>круг и квадрат это одна фигура эээ не совсем корректно Вопрос: Имеются ли какие-нибудь препятствия для того, чтобы представить и круг, и квадрат одним классом? Например:
class TFigure
{
public:
// ...protected:
std::vector<TPoint> m_Points;
};
Здравствуйте, Alxndr, Вы писали:
КЛ>>1) В Microsoft Word XP я насчитал 114 автофигур. И это не считая того, что в Word'е есть возможность добавить другие автофигуры. Вы представляете, сколько надо классов, если придерживаться правила "по классу на кажду фигуру"?
A>И что с того? Оверхед в 114 виртуальных таблиц? A>И самое главное: что это доказывает?
А теперь представьте, необходимо добавить новую фигуру: СложноЗакругленныйПрямоугольникСДырочкойВПравомВерхнемУглу. В таком случае вам придеться добавлять код обработки этой фигуры во все N мест где эта фигура может встретиться.
Что касаеться реального примера, будет вам его В документации UML редактора который я в данный момент начинаю разрабатывать с нуля, обьекты описаны грузящимиси из файлов XML описаний в совокупности с векторными граф. файлами. Таким образом. существует какие-нибудь два базовых класса: uobjectwidget и uconnectorwidget которые, скажем, могут описывать вектор из точек, линий и цветов которые уже создаються какой-нибудь uml_object_factory по запросу на создание определенного обьекта. ИМХО, это разумнее чем описывать классы upackage, uclass, uaggregateconnector, uinheritconnector и т.д и т.п.
Здравствуйте, genre, Вы писали:
G>есть одно небольшое препятствие. для круга m_Points.size() будет стремится к бесконечности.
Вовсе нет. Коллега сказал, что максимальное количество точек для круга — 3600. Но и это можно оптимизировать. Круг и эллипс легко представляются с помощью 4-х кривых Безье 3-го порядка. Для задания каждой такой кривой необходимы 4 точки. Соответственно, для 4-х состыкованных кривых, представляющих круг, нужно всего 12 (!) точек.
Если не подойдут кривые, можно использовать arc'и, которые дают более точное представление круга и эллипса.
В любом случае, фигура представляется набором точек, которые могут быть:
Собственно, мы ничего нового не придумываем. В языке векторной графики PostScript уже давно выделены эти 3 примитива, и с помощью них представляется любая картинка.