Кирилл Лебедев wrote: > G>ну так предложите свой способ. > Можно использовать массив указателей на функции.
Ага, это называется "восход солнца вручную". Вы просто повторите
механизм полиморфизма, но уже руками без поддержки компилятора.
Такой трюк в мире С называется vtable (virtual table ) и используется
для реализации COM-объектов (интерфейсы которых являются просто набором
указателей на функции).
> G>его размер зависит от очень большого количества параметров. > Это не так. Размер свитча вряд ли будет меняться, т.к. набор примитивов > для представления фигур разных форм исчерпывается отрезком и кривой > Безье 3-го порядка. Как исключение, можно добавить еще и дугу эллипса.
Ага, а еще нужны битмапы (для рисунков) и шрифты (для текста). Причем
шрифты бывают двух видов: растровые и векторные с соответствующими
различиями в поведении. А еще могут понадобиться примитивы заливки или
CSG-операций. А потом захочется OLE-объекты вставлять (диаграммы из MS
Graph, например). А потом...
Здравствуйте, SleepyDrago, Вы писали:
SD>то у меня вполне конкретный вопрос к Вам (повторю еще раз):
После того, как мы определимся с кругом и квадратом (это существенный момент), я Вам отвечу на этот вопрос.
SD>зы "круг" в моих ТЗ это многоугольник с заданным количеством точек (от 4 до 3600) в зависимости от настроения пользователя так что утверждать что SD>круг и квадрат это одна фигура эээ не совсем корректно Вопрос: Имеются ли какие-нибудь препятствия для того, чтобы представить и круг, и квадрат одним классом? Например:
class TFigure
{
public:
// ...protected:
std::vector<TPoint> m_Points;
};
Здравствуйте, Дядюшка Че, Вы писали:
ДЧ>Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации 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. На сложной предметной области или при возрастании количества разнородных требований обычную иерархию классов просто "разносит". Иерархия оказывается нежизнеспособной.
Здравствуйте, Alxndr, Вы писали:
A>Можно услышать обоснование "принципиальной ошибочности"?
Можно. Их несколько:
1) В Microsoft Word XP я насчитал 114 автофигур. И это не считая того, что в Word'е есть возможность добавить другие автофигуры. Вы представляете, сколько надо классов, если придерживаться правила "по классу на кажду фигуру"?
2) Фигуры нужны для того, чтобы над ними выполнять операции. Если потребуется изменять форму, например, с помощью инструмента Shape, то правило "по классу на каждую фигуру" опять-таки не сработает, т.к. в результате преобразования формы одна фигуры может легко преобразоваться в другую. Например, roundrect легко можно преобразовать и в прямоугольник, и в эллипс.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, remark, Вы писали:
R>> Cyberax уже успеет слабать пару десятков классов КЛ>Я даже не сомневаюсь, что кто-то, пока продумывается архитектура, способен "сбацать" пару десятков классов. Не сомневаюсь я и в том, что пока разрабатывается архитектура дома или моста, иной "удачливый" строитель способен "сбацать" дом или мост. Только вот кто в этом доме будет жить и кто этим мостом будет пользоваться?
КЛ>Вопрос не в том, чтобы "сбацать". Вопрос в том, чтобы все это грамотно сопровождать.
Не всегда.
А что по поводу последнего вопроса: где и как размещать всё остальное, кроме рисования, что связано с фигурами?
> Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации Draw > наружу. Также хочется оставить за собой возможность абстрагироваться от HDC и > поддерживать отрисовку не только на нем, но и, скажем, на поверхностях DirectX и > в GDI+.
> Думается, что реализации Draw должны располагаться в отдельных иерархиях, но > быть в то же время привязанными к основной иерархии.
> Пример надуманный, но зато довольно наглядный. Чувствую, паттерны должны помочь; > но какой/какие из них?
Здравствуйте, Дядюшка Че, Вы писали:
ДЧ>Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации Draw наружу. Также хочется оставить за собой возможность абстрагироваться от HDC и поддерживать отрисовку не только на нем, но и, скажем, на поверхностях DirectX и в GDI+.
ДЧ>Думается, что реализации Draw должны располагаться в отдельных иерархиях, но быть в то же время привязанными к основной иерархии.
Т.о. есть два базавых класса: Shape и DrawContext. И для каждой пары наследников этих классов теперь нужно определить операцию(операции) взаимодействия (отрисовка, перемещение). Это есть ни что иное, как известная задача о двойной диспетчеризации. Паттерн, подходящий, для решения этой задачи — это Visitor (Посетитель). Недостаток этого подхода — необходимость модифицировать базовый класс при добавлении новых классов. Другой способ — держать таблицу обработчиков парных взаимодействий, у этого подхода тоже есть свои оригинальные решения, преимущества и недостатки.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, remark, Вы писали:
R> Cyberax уже успеет слабать пару десятков классов
Я даже не сомневаюсь, что кто-то, пока продумывается архитектура, способен "сбацать" пару десятков классов. Не сомневаюсь я и в том, что пока разрабатывается архитектура дома или моста, иной "удачливый" строитель способен "сбацать" дом или мост. Только вот кто в этом доме будет жить и кто этим мостом будет пользоваться?
Вопрос не в том, чтобы "сбацать". Вопрос в том, чтобы все это грамотно сопровождать.
Здравствуйте, Left2, Вы писали:
L>>>Как вариант — иерархия renderer-ов и их использование через паттерн decorator. LM>>Декоратор тут явно не при чем
L>Это почему же? L>В GoF был довольно похожий пример про "просто конрол" и "контрол с рамочкой".
Был. L>Если Shape-ы слабо связаны между собой по иерархии, но отрисовываются похожим образом, или если отрисовка может меняться на лету в зависимости от состояния Shape — может подойти. L>ИМХО, конечно.
Но тут вроде совсем не тот случай
Здравствуйте, Кирилл Лебедев, Вы писали:
A>>Можно услышать обоснование "принципиальной ошибочности"?
КЛ>Можно. Их несколько:
КЛ>1) В Microsoft Word XP я насчитал 114 автофигур. И это не считая того, что в Word'е есть возможность добавить другие автофигуры. Вы представляете, сколько надо классов, если придерживаться правила "по классу на кажду фигуру"?
И что с того? Оверхед в 114 виртуальных таблиц?
И самое главное: что это доказывает?
КЛ>2) Фигуры нужны для того, чтобы над ними выполнять операции. Если потребуется изменять форму, например, с помощью инструмента Shape, то правило "по классу на каждую фигуру" опять-таки не сработает, т.к. в результате преобразования формы одна фигуры может легко преобразоваться в другую. Например, roundrect легко можно преобразовать и в прямоугольник, и в эллипс.
Ты привел конкретную задачу, в которой иерархия фигур смысла действительно не имеет.
Очевидно, "принципиальную ошибочность" подхода с иерархией фигур это никак не доказывает.
Здравствуйте, genre, Вы писали:
G>есть одно небольшое препятствие. для круга m_Points.size() будет стремится к бесконечности.
Вовсе нет. Коллега сказал, что максимальное количество точек для круга — 3600. Но и это можно оптимизировать. Круг и эллипс легко представляются с помощью 4-х кривых Безье 3-го порядка. Для задания каждой такой кривой необходимы 4 точки. Соответственно, для 4-х состыкованных кривых, представляющих круг, нужно всего 12 (!) точек.
Если не подойдут кривые, можно использовать arc'и, которые дают более точное представление круга и эллипса.
В любом случае, фигура представляется набором точек, которые могут быть:
Собственно, мы ничего нового не придумываем. В языке векторной графики PostScript уже давно выделены эти 3 примитива, и с помощью них представляется любая картинка.
Здравствуйте, Cyberax, Вы писали:
C>Ага, одним классом с баааальшим switch'ем.
Откуда Вы взяли огромный switch?
C>Почему? Преобразования замечательно делаются в таком подходе — мы просто C>выделяем трансформаторы в отдельную иерархию.
Я говорил о двух вещах:
1) О том, что иерархия начинает разрастаться, и ее трудно сопровождать. Согласитесь, 115 классов — это уже перебор.
2) О том, что фигуры становится трудно трансформировать. Как пример, Вам нужно сделать скос эллипса или прямоугольника. Если Вы задаете эллипс при помощи rect'а (левая верхняя и правая нижняя точки), то скос без создания новой фигуры (например, полигона) Вам будет сделать сложно.
C>Кхм. Именно сейчас это и делаю — ошибочности подхода пока не вижу.
Вам приходится:
1) писать класс для каждой фигуры;
2) писать иерархию классов для преобразования фигур.
На мой взгляд, это трудоемко да и не нужно. Понятно, что у Вас может быть другое мнение.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, SleepyDrago, Вы писали:
SD>>Было бы интересно увидеть развитие ваших идей до интерфейса исходя из базового примера фигур круг/квадрат и 2х устройств просмотра. КЛ>Как ни странно, круг и квадрат — это не две разных фигуры, а одна. Т.е. иерархия классов в стиле: КЛ>
КЛ>// Базовый класс фигуры.
КЛ>class TFigure {...};
КЛ>// Круг.
КЛ>class TCircle : public TFigure {...};
КЛ>// Квадрат.
КЛ>class TQuadrate : public TFigure {...};
КЛ>
КЛ>- принципиально ошибочна.
Я понял причину Вашего спора — если поглядеть на название данного форума, то Вы находитесь по разные стороны от "/"
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, genre, Вы писали:
G>>Надо разделить действительно разные сущности, например полигоны, и кривые безье. КЛ>Можно спросить: почему Вы решили, что полигоны и кривые Безье — это разные сущности? (Вопрос серьезный.)
да я не решал
в каком-то из сообщений треда так было разделено я и написал что в голову первое пришло.
Кирилл Лебедев wrote: > Код рисования фигуры на устройстве будет выглядеть так:
Вы прямо цитируете пример из учебника как НЕ надо писать код и для чего
был придуман полиморфизм. Вы это специально?
> C>Далее, рисование окружности/эллипса происходит в десятки раз быстрее, > C>чем рисование кривых Безье. А значит для нормальной скорости нам нужно > C>добавить в фигуру еще и набор эллипсов: > Глифы TrueType и Type1 шрифтов представляют собой комбинацию отрезков > прямых линий и кривых Безье. Как видите, выводятся нормально — не тормозят.
Простите, я знаю что говорю. Тот же GDI рисует окружности _намного_
быстрее кривых Безье. Хотя бы из-за того, что не всегда нормально
работает аппаратное ускорение для кривых Безье.
> Алгоритм же рисования повернутого эллипса, который я вычитал в одной из > "умных" книжек, настолько непростой и глючный, что — ей богу — проще > использовать кривые Безье. Собственно говоря, профессиональные пакеты > для создания векторной графики так и поступают. То же Corel Draw.
Вы знаете внутренности CD?
> C>А ведь в фигуре еще может содержаться текст! Или фигура может состоять > C>из композиции нескольких фигур. > Я не случайно обратил Ваше внимание на язык PostScript. Там эта проблема > уже решена.
Еще раз. PS, PDF, bitmap — это конечные _представления_. Они не
предоставляют нормальной поддержки для манипуляции объектами.
> C>Например, как в вашем случае будет выглядеть изменение радиуса окружности? > Очень просто. Я пересчитаю точки фигуры точно так же, как если бы это > был полигон.
_КАК_ вы определите какие точки принадлежат окружности?
Здравствуйте, Cyberax, Вы писали:
C>Когда? Вы не привели ни одного классического примера вредности OOP (типа C> "brittle base class problem").
1) Я не говорил о вредности ООП вообще. Эту мысль приписали мне Вы.
2) Я говорил о вредности применения иерархии классов в данном конкретном случае. Вот ссылки на мои посты: http://www.rsdn.ru/Forum/Message.aspx?mid=1692826&only=1
C>Если вы не поняли, то в этом сообщении я привел недостатки _ОТСУТСТВИЯ_ C>полиморфизма.
Поправлюсь. В Вашем примере явственно видны недостатки подхода "по классу на каждую фигуру". Если будет полиморфизм, то недостатков станет меньше. Тем не менее, они будут.
Например: из-за того, что сегменты пути (отрезки прямых линий и кривые Безье) реализованы в виде классов, описание пути нужно хранить в виде набора указателей:
// Базовый класс для сегмента пути.class TPathSegment {...};
// Класс для отрезка.class TLineSegment : public TPathSegment {...};
// Класс для кривой Безье.class TCurveSegment : public TPathSegment {...};
// Путь - контур фигуры.class TPath
{
public:
// ...private:
std::vector<TPathSegment *> m_Segments;
};
// Фигура.class TFigure
{
public:
// ...private:
TPath m_Path;
};
Это решение обладает такими недостатками:
1) Не позволяет интерпретировать путь, как набор точек (пусть и со своими флагами). Т.е. вводит дополнительный уровень абстракции, который в данном случае не нужен. (Устранение свитча из 3 (трех!) case'ов не назовешь особой нуждой.)
2) При добавлении нового сегмента пути необходимо выделять память под сегмент, т.е. использовать оператор new. В то время, как в путь, состоящий из точек, мне достаточно "запихнуть" необходимое количество точек (1 или 3).
C>0. Не описывает всех возможных случаев. C>1. Достаточно 0).
Бездоказательно. Какие конкретно случаи оно не описывает?
Язык PostScript существует и развивается уже довольно значительное время. За все это время к пути не понадобилось добавить никакого другого примитива. Пока что обходятся отрезками и кривыми Безье.
C>Я говорю совершенно профессионально — ваш код взят из примеров "как не C>надо писать" из учебников по ООП. Привести конкретные ссылки?
Мне не нужны никакие ссылки на чей-то другой код. Поверьте, я могу и сам привести немало подобных ссылок. Если Вы не видите разницу между архитектурным решением в чужом коде и архитектурным решением, предложенным мной, и свои выводы делаете только на внешней похожести, то это просто говорит о недопонимании Вами некоторых глубинных вещей, относящихся к построению абстрактных моделей.
Чтобы выявить это недопонимание или, наоброт, выяснить, что все в порядке, и чего-то не допонимаю я, а не Вы, необходимо, чтобы Вы перечислили конкретные недостатки именно моего кода и моего решения. Так что жду от Вас конкретных аргументов.
>> И что с того? К чему нам такое ускорение, если оно нам *не нужно*? >> Повторюсь, глифы *TrueType* и *Type1* шрифтов используют кривые Безье. C>А монитор использует пиксели. И что с того?
Приведенный мною пример говорит о том, что сложные контуры (а контуры символов в шрифтах) можно описать комбинацией отрезков и кривых Безье.
Кроме того, Ваш аргумент о скорости не "прокатывает", т.к. раз она достаточна для того, чтобы рисовать шрифты, ее хватит и для того, чтобы рисовать фигуры, в которых гораздо меньше кривых, чем в шрифтах.
C>_КАК_ вы планируете делать осмысленые манипуляции с текстом (изменение C>букв, например), представленном в виде набора кривых?
Растеризация символов шрифта в последовательность контуров происходит на другом уровне абстракции. Т.е. она относится к обязанностям устройства, а не фигуры.
Но и этот пример можно использовать для подтверждения правильности моего подхода. Если следовать Вашей логике, то разработчики шрифтов (или растеризаторов для шрифтов) должны писать по классу на каждый глиф. Ведь глиф — это отдельная фигура. Тогда должно было получиться что-то вроде:
// Глиф, базовый класс.class TGlyph {...};
// Буква A.class TGlyphA : public TGlyph {...};
// Буква B.class TGlyphB : public TGlyph {...};
// Буква C.class TGlyphC : public TGlyph {...};
// И т.д.
C>Вот у вас массив действий (вправо-вверх-на-10 вправо-вниз-на-4 C>влево-на-4 вправо-на-4 вправо-вниз-на-4). Что это такое?
См. пример со шрифтами. Хотя пока что непонятно, зачем Вам в коде нужно знать, работаете ли Вы с прямоугольником или с эллипсом.
C>Где? Как мне в PS прочитать и изменить текст, представленый в виде C>набора кривых?
Текст преобразуется в контуры на уровне устройства. До этого момента с текстом работают, как с набором символов. Если же Вам (не пользователю, а Вам) необходимо различать, с какой фигурой Вы работаете, то можно использовать подход, с успехом применяющийся для текста: дать каждой фигуре определенный идентификатор (целочисленный или строковый).
Более детально о языке PostScript написано вот здесь: http://partners.adobe.com/public/developer/en/ps/psrefman.pdf
C>То есть у вас в редакторе будет еще и интеллектуальный распознаватель C>фигур (вместе с OCR'ом и разумным демоном, вестимо).
Нет. Зачем? У меня есть устройство, которое с заданной точностью преобразует мне путь в полигон.
C>Напоминаю, у нас есть _только_ набор линий.
Набор линий существует внутри фигуры. Никто не возражает, если фигура (помимо линий) будет содержать еще и название.
Здравствуйте, Cyberax, Вы писали:
C>А теперь представьте как в вашей модели изменять радиус окружности или C>шрифт у надписи.
А вместо радиуса поменять диаметр слабо?
Рекомендую обратить внимание на ссылки (в них есть смысл):
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Забыл добавить. Преимущество такого подхода — мы можем добавлять новые фигуры без изменения исходного кода и, следовательно, без перекомпиляции программы.
Такой подход тоже хорошо. Но тут надо чётко осознавать размер проекта и требования к сопровождаемости, т.к. в Вашем подходе вводится мощный и толстый уровень абстракции. Пока Вы будете разрабатывать формат внешнего XML файла, писать загрузку, интерпретацию и т.д. Cyberax уже успеет слабать пару десятков классов
По поводу добавления новых фигур, ну тут и кодирование фигур не уступает — ничто не мешает мне подложить новую dll'ку с фигурами без перекомпиляции основного проекта.
Ещё такой принципиальный вопрос по поводу Вашего подхода: то, что Вы описали относиться только к рисованию фигур, но с фигурами обычно связано ещё много чего: название, картинка (ну хотя это тоже можно поместить во внешний файл), способ размещения юзером (например, окружность можно размещать по 2 точкам, по 3 точкам и т.д.), способ отмены рисования фигуры, список свойств (радиус для окружности, количество точек для полигона).
В общем что я хочу сказать, в большинстве приложений с каждой фигурой связано много чего, и просто редактированием внешнего файла для добавления фигуры тут не обойтись. Как Вы предлагаете связывать с фигурой всю эту информацию?
Здравствуйте, michus, Вы писали:
M>Если окружность — отдельный объект, то радиус можно менять увеличением/уменьшением размеров прямоугольника элемента. Заодно эта окружность становится эллипсом, если высота не равна ширине.
В предложенной мною модели все так и происходит. Радиусы эллипса (окружности) изменяются заданием высоты и ширины. И для этого совсем не надо заводить для окружности отдельный класс.
Интересный вопрос возник, все никак не родить решения...
Есть иерархия классов (чисто в теории)
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 — может подойти.
ИМХО, конечно.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>3. Наконец, последний и самый абстрактный вариант — это отказаться от способа построения документа, как контейнера указателей на классы некоей иерархии. Лучше представить документ в виде базы данных, к которой можно сформировать ряд запросов. В этом случае получается, что каждый просмотр (вид) обращается к базе данных (документу) с запросом тех данных, которые ему (виду) нужны. Таким образом, получается разделение обязанностей:
КЛ>1) Документ предоставляет необходимые данные по запросу (для этого у него есть соответствующий интерфейс). КЛ>2) Каждый просмотр (вид) получает нужные ему данные и отображает их нужным образом.
КЛ>Есть очень серьезное предположение, что профессиональные редакторы спроектированы по Модели 3. На сложной предметной области или при возрастании количества разнородных требований обычную иерархию классов просто "разносит". Иерархия оказывается нежизнеспособной.
Было бы интересно увидеть развитие ваших идей до интерфейса исходя из базового примера фигур круг/квадрат и 2х устройств просмотра.
а то использование слов "база данных" наводит на нехорошие мысли.
wbr
Здравствуйте, SleepyDrago, Вы писали:
SD>Было бы интересно увидеть развитие ваших идей до интерфейса исходя из базового примера фигур круг/квадрат и 2х устройств просмотра.
Как ни странно, круг и квадрат — это не две разных фигуры, а одна. Т.е. иерархия классов в стиле:
// Базовый класс фигуры.class TFigure {...};
// Круг.class TCircle : public TFigure {...};
// Квадрат.class TQuadrate : public TFigure {...};
ДЧ>Хотелось бы вынести отделить отрисовку. То есть вынести детали реализации 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) в зависимости от настроения пользователя так что утверждать что
круг и квадрат это одна фигура эээ не совсем корректно
Здравствуйте, Alxndr, Вы писали:
КЛ>>1) В Microsoft Word XP я насчитал 114 автофигур. И это не считая того, что в Word'е есть возможность добавить другие автофигуры. Вы представляете, сколько надо классов, если придерживаться правила "по классу на кажду фигуру"?
A>И что с того? Оверхед в 114 виртуальных таблиц? A>И самое главное: что это доказывает?
А теперь представьте, необходимо добавить новую фигуру: СложноЗакругленныйПрямоугольникСДырочкойВПравомВерхнемУглу. В таком случае вам придеться добавлять код обработки этой фигуры во все N мест где эта фигура может встретиться.
Что касаеться реального примера, будет вам его В документации UML редактора который я в данный момент начинаю разрабатывать с нуля, обьекты описаны грузящимиси из файлов XML описаний в совокупности с векторными граф. файлами. Таким образом. существует какие-нибудь два базовых класса: uobjectwidget и uconnectorwidget которые, скажем, могут описывать вектор из точек, линий и цветов которые уже создаються какой-нибудь uml_object_factory по запросу на создание определенного обьекта. ИМХО, это разумнее чем описывать классы upackage, uclass, uaggregateconnector, uinheritconnector и т.д и т.п.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>1) точками отрезков прямых линий; КЛ>2) точками кривых Безье 3-го порядка; КЛ>3) точками arc'ов.
КЛ>Собственно, мы ничего нового не придумываем. В языке векторной графики PostScript уже давно выделены эти 3 примитива, и с помощью них представляется любая картинка.
вот эти три класса и должны быть в иерархии классов
Кирилл Лебедев wrote: > G>есть одно небольшое препятствие. для круга m_Points.size() будет > стремится к бесконечности. > Вовсе нет. Коллега сказал, что максимальное количество точек для круга — > 3600. Но и это можно оптимизировать. Круг и эллипс легко представляются > с помощью 4-х кривых Безье 3-го порядка. Для задания каждой такой кривой > необходимы 4 точки. Соответственно, для 4-х состыкованных кривых, > представляющих круг, нужно всего 12 (!) точек.
Но тогда у вас фигуры будут выглядеть так:
То есть даже в прямоугольнике будут ненужные описания кривых.
Далее, рисование окружности/эллипса происходит в десятки раз быстрее,
чем рисование кривых Безье. А значит для нормальной скорости нам нужно
добавить в фигуру еще и набор эллипсов:
А ведь в фигуре еще может содержаться текст! Или фигура может состоять
из композиции нескольких фигур.
> Собственно, мы ничего нового не придумываем. В языке векторной графики > PostScript уже давно выделены эти 3 примитива, и с помощью них > представляется любая картинка.
А к чему усложнять? Любая реальная фигура представима в виде набора
окружностей достаточно маленького радиуса.
Нам ведь кроме представления еще нужно и уметь осмысленно манипулировать
фигурами.
Например, как в вашем случае будет выглядеть изменение радиуса окружности?
Кирилл Лебедев wrote: > A>И что с того? Оверхед в 114 виртуальных таблиц? > A>И самое главное: что это доказывает? > Дело не в 114 виртуальных таблицах, а в трудоемкости создания и > сопровождения 114 классов фигур. Это бессмысленно тем более, что можно > обойтись всего *одним (1!)* классом.
Ага, одним классом с баааальшим switch'ем.
> Принципиальная ошибочность доказывается очень просто: с увеличением > разнообразия фигур и операций по их преобразованию "стройная" иерархия > классов в стиле подхода *"по классу на фигуру"* просто разлетается.
Почему? Преобразования замечательно делаются в таком подходе — мы просто
выделяем трансформаторы в отдельную иерархию.
> Жаль, если это *Вам* непонятно. Возможно, *Вам* имеет смысл > посопровождать ряд графических редакторов, написанных в стиле "по классу > на фигуру" и, так сказать, воочию убедиться в ошибочности данного подхода.
Кхм. Именно сейчас это и делаю — ошибочности подхода пока не вижу.
У меня сделано так:
1. Базовый класс (точнее интерфейс) IWidget имеет методы для проверки
попадания, передвижения, вращения и описания регионов (то есть областей,
которые объект может закрывать) объекта.
2. У IWidget'а есть наследники IConnectorWidget, ITextWidget...
3. Есть специальный интерфейс ICompositeWidget, позволяющий задавать
виджеты, состоящие из нескольких дочерних виджетов.
4. Есть конкретные потомки ICustomDrawWidget (с потомком IActiveXWidget)
и т.п.
Для трансформации используется механизм FOURCC (Four-Character Code) —
каждый виджет экспортирует набор FOURCC в которые он может превращаться,
и у каждого типа виджетов есть свой FOURCC. Если мне нужно преобразовать
один тип в другой — то трансформатор просто смотрит можно ли построить
цепочку преобразований из одного FOURCC в другой.
Здравствуйте, genre, Вы писали:
G>вот эти три класса и должны быть в иерархии классов
Возможно. А зачем их (отрезок, кривую и дугу) представлять в виде классов?
Код рисования фигуры на устройстве будет выглядеть так:
size_t i = 0;
while (i < m_Points.size())
{
switch (m_Flags[i])
{
case PF_MOVE:
dc.MoveTo(m_Points[i]);
i++;
break;
case PF_LINE:
dc.LineTo(m_Points[i]);
i++;
break;
case PF_CURVE:
ASSERT((i + 2) < m_Points.size());
dc.CurveTo(m_Points[i], m_Points[i + 1], m_Points[i + 2]);
i += 3;
break;
}
}
C>То есть даже в прямоугольнике будут ненужные описания кривых.
Как видите, не будет.
C>Далее, рисование окружности/эллипса происходит в десятки раз быстрее, C>чем рисование кривых Безье. А значит для нормальной скорости нам нужно C>добавить в фигуру еще и набор эллипсов:
Глифы TrueType и Type1 шрифтов представляют собой комбинацию отрезков прямых линий и кривых Безье. Как видите, выводятся нормально — не тормозят.
Алгоритм же рисования повернутого эллипса, который я вычитал в одной из "умных" книжек, настолько непростой и глючный, что — ей богу — проще использовать кривые Безье. Собственно говоря, профессиональные пакеты для создания векторной графики так и поступают. То же Corel Draw.
C>А ведь в фигуре еще может содержаться текст! Или фигура может состоять C>из композиции нескольких фигур.
Я не случайно обратил Ваше внимание на язык PostScript. Там эта проблема уже решена.
C>Например, как в вашем случае будет выглядеть изменение радиуса окружности?
Очень просто. Я пересчитаю точки фигуры точно так же, как если бы это был полигон.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, genre, Вы писали:
G>>вот эти три класса и должны быть в иерархии классов КЛ>Возможно. А зачем их (отрезок, кривую и дугу) представлять в виде классов?
а что б switch-ей не писать на каждый чих.
C>>Кхм. Именно сейчас это и делаю — ошибочности подхода пока не вижу. КЛ>Вам приходится: КЛ>1) писать класс для каждой фигуры; КЛ>2) писать иерархию классов для преобразования фигур.
КЛ>На мой взгляд, это трудоемко да и не нужно. Понятно, что у Вас может быть другое мнение.
Выскажу свое мнение. В один класс все запихивать действительно не надо.
И Делать развесистую иерархию тоже не стоит.
Надо разделить действительно разные сущности, например полигоны, и кривые безье. у иж из них делать иерархию, в итоге она получится маленькая и по делу.
А еще лучше как-то так(упрощенно):
class Element{
virtual void Draw()=0;
}
class Polygon:public Draw{
...
}
class Figure{
...
std::vector<Element*> elements;
}
вот собственно Figure и будет самой фигурой, которая агрегирует в себе различные элементы.
но вообще все сильно зависит от задачи.
требования эффективности могут накладывать свои ограничения.
Здравствуйте, genre, Вы писали:
G>Надо разделить действительно разные сущности, например полигоны, и кривые безье.
Можно спросить: почему Вы решили, что полигоны и кривые Безье — это разные сущности? (Вопрос серьезный.)
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, genre, Вы писали:
G>>а что б switch-ей не писать на каждый чих. КЛ>Какие конкретно switches Вы хотите убрать этой иерархией? Re[13]: Параллельная иерархия классов
Кирилл Лебедев wrote: > C>Ага, одним классом с баааальшим switch'ем. > Откуда Вы взяли огромный switch?
Смотрите свое другое письмо
> C>Почему? Преобразования замечательно делаются в таком подходе — мы просто > C>выделяем трансформаторы в отдельную иерархию. > Я говорил о двух вещах: > 1) О том, что иерархия начинает разрастаться, и ее трудно сопровождать. > Согласитесь, 115 классов — это уже перебор.
Всего 115 классов? Какая мелочь, у меня их за тысячу перевалило.
Вы лучше подумайте как вы метод 'Draw' со switch'ем в 115 case'ов
сопровождать будете.
> 2) О том, что фигуры становится трудно трансформировать. Как пример, Вам > нужно сделать скос эллипса или прямоугольника.
Заданием матрицы отображения. При этом координаты точек модели не
меняются, а просто изменяется метод их перевода в глобальные координаты.
Именно так и работают все нормальные рисовальные системы, кстати.
GDI, кстати, это прямо поддерживает — смотрите метод SetWorldTransform.
> C>Кхм. Именно сейчас это и делаю — ошибочности подхода пока не вижу. > Вам приходится: > 1) писать класс для каждой фигуры;
Да.
> 2) писать иерархию классов для преобразования фигур. > На мой взгляд, это *трудоемко* да и *не нужно*. Понятно, что у Вас может > быть другое мнение.
Расскажите как это сделать правильно и проще. Я не вижу более простого
способа.
Кирилл Лебедев wrote: > G>Надо разделить действительно *разные* сущности, например полигоны, и > кривые безье. > Можно спросить: почему Вы решили, что полигоны и кривые Безье — это > *разные сущности*? (Вопрос серьезный.)
Кривые поддерживают принципиально больше возможностей управления. И при
этом принципиально медленнее рисуются.
1) Кроме иерархии классов существуют еще и другие способы устранения свитчей. Вы уверены, что именно этот свитч нужно устранять именно иерархией классов?
2) Вы уверены, что этот свитч нужно вообще устранять? Его размер ограничен, и он никогда не разрастется.
А>1) Кроме иерархии классов существуют еще и другие способы устранения свитчей. Вы уверены, что именно этот свитч нужно устранять именно иерархией классов?
ну так предложите свой способ. А>2) Вы уверены, что этот свитч нужно вообще устранять? Его размер ограничен, и он никогда не разрастется.
его размер зависит от очень большого количества параметров.
вполне может быть что и тремя классами ограничится не получится, отличия же могут быть не только по типу фигуры.
Здравствуйте, Cyberax, Вы писали:
C>Вы прямо цитируете пример из учебника как НЕ надо писать код и для чего C>был придуман полиморфизм. Вы это специально?
Полиморфизм — лишь один из инструментов организации кода, который в ряде случаев не только не полезен, но и вреден. Где-то выгоднее использовать иерархию и виртуальные функции, где-то — массив и указатели, где-то — перечисление, где-то — union, где-то — просто структуру.
Сожаления о том, что там не используется полиморфизм, эмоционально понятны, но профессионально "не прокатывают".
C>Простите, я знаю что говорю. Тот же GDI рисует окружности _намного_ C>быстрее кривых Безье. Хотя бы из-за того, что не всегда нормально C>работает аппаратное ускорение для кривых Безье.
И что с того? К чему нам такое ускорение, если оно нам не нужно? Повторюсь, глифы TrueType и Type1 шрифтов используют кривые Безье.
C>Вы знаете внутренности CD?
Мне приходилось проводить research на тему того, как лучше организовать классы фигур для редактора векторной графики.
C>Еще раз. PS, PDF, bitmap — это конечные _представления_. Они не C>предоставляют нормальной поддержки для манипуляции объектами.
1) Bitmap упомянули Вы, а не я.
2) Что касается PS и PDF, то тут Вы ошибаетесь.
В любом случае, приведите конкретные примеры, чтобы было понятно, о чем речь.
C>_КАК_ вы определите какие точки принадлежат окружности?
Преобразую в полигон и проверю.
Здравствуйте, Аноним, Вы писали:
А>1) Кроме иерархии классов существуют еще и другие способы устранения свитчей. Вы уверены, что именно этот свитч нужно устранять именно иерархией классов?
Перечислите эти способы, пожалуйста, с объяснениями чем оно принципиально отличается.
Вместо полиморфизма, конечно, можно использовать объект с кучей делегатов, например. Или аналогичные извращения — смысл от этого не изменится, так как все равно останется точки использования полиморфного поведения.
А>2) Вы уверены, что этот свитч нужно вообще устранять? Его размер ограничен, и он никогда не разрастется.
Во-первых, кроме этого свитча будет еще с десяток подобных (в других методах). Во-вторых, этот свитч очень даже разрастется.
У меня моим первым детским проектом был растровый редактор на Бейсике (QuickBasic'е, а не на VisualBasic!), потом этот растровый редактор стал векторным и там был как раз такой свитч. При размере редактора в 140Кб кода на Бейсике поддерживать основную функцию рисования стало невозможно У меня там в итоге даже свой рендерер шрифтов был (шрифты сам рисовал с помощью "черепашьей графики").
Здравствуйте, genre, Вы писали:
G>ну так предложите свой способ.
Можно использовать массив указателей на функции.
G>его размер зависит от очень большого количества параметров.
Это не так. Размер свитча вряд ли будет меняться, т.к. набор примитивов для представления фигур разных форм исчерпывается отрезком и кривой Безье 3-го порядка. Как исключение, можно добавить еще и дугу эллипса.
Здравствуйте, Cyberax, Вы писали:
C>Кривые поддерживают принципиально больше возможностей управления. И при C>этом принципиально медленнее рисуются.
Действительно, это так. Однако на этих особенностях имеет смысл концентрироваться, если они имеют существенное значение для задачи. С точки зрения фигуры эти различия не важны.
Кривые и отрезки прямых линий — это сегменты пути, который формирует контур фигуры. И потому мы смотрим на них одинаково.
Именно в этой нехитрой мысли и заключается абстрагирование. А, конечно же, не в том, чтобы наплодить иерархию классов, которая не нужна.
Аноним wrote: > C>Кривые поддерживают принципиально больше возможностей управления. И при > C>этом принципиально медленнее рисуются. > Действительно, это так. Однако на этих особенностях имеет смысл > концентрироваться, если они имеют существенное значение для задачи. С > точки зрения фигуры эти различия не важны.
Ну да, истребитель Миг-29 или бумажный самолетик. Различия не важны —
ведь оба описываются конструкторскими чертежами и оба являются самолетами.
У окружности в графическом редакторе есть осмысленый параметр — радиус.
Во всех нормальных редакторах я могу напрямую менять его. А теперь
расскажите мне как это нормально сделать без аналога полиморфизма.
> *Кривые и отрезки прямых линий — это сегменты пути, который формирует > контур фигуры.* И потому мы смотрим на них *одинаково*.
Ну да. А пиксели формируют изображение на мониторе, поэтому DOS и Linux
— одно и то же.
> Именно в этой нехитрой мысли и заключается *абстрагирование*. А, конечно > же, не в том, чтобы наплодить иерархию классов, которая не нужна.
Ну так покажите простой пример: псевдокод редактора, который позволит
задавать окружности и прямоугольники. Для окружности должна быть
возможность изменения радиуса, а для квадрата — изменения длин сторон.
C>Всего 115 классов? Какая мелочь, у меня их за тысячу перевалило.
Надеюсь, Вы хорошо знаете, что делаете. И о том, как все это количество классов сопровождать, Вы тоже подумали.
C>Вы лучше подумайте как вы метод 'Draw' со switch'ем в 115 case'ов C>сопровождать будете.
Вы невнимательно читаете. Откуда Вы взяли 115 case'ов?
C>GDI, кстати, это прямо поддерживает — смотрите метод SetWorldTransform.
Такая возможность появилась в GDI недавно.
C>Расскажите как это сделать правильно и проще. Я не вижу более простого C>способа.
Чуть позже расскажу.
Здравствуйте, Cyberax, Вы писали:
C>Ну да, истребитель Миг-29 или бумажный самолетик. Различия не важны — C>ведь оба описываются конструкторскими чертежами и оба являются самолетами.
С точки зрения "устройства, напичканные разными приборами" это, действительно, разные вещи. С точки зрения "устройства, которые летают" они одинаковы.
C>У окружности в графическом редакторе есть осмысленый параметр — радиус. C>Во всех нормальных редакторах я могу напрямую менять его. А теперь C>расскажите мне как это нормально сделать без аналога полиморфизма.
В Microsof Word'е, например, не можете.
Кирилл Лебедев wrote: > C>Вы прямо цитируете пример из учебника как НЕ надо писать код и для чего > C>был придуман полиморфизм. Вы это специально? > Полиморфизм — лишь один из инструментов организации кода, который в ряде > случаев не только не полезен, но и *вреден*.
Когда? Вы не привели ни одного классического примера вредности OOP (типа
"brittle base class problem").
> Недостатки полиморфизма *для данного конкретного случая* я уже привел. > Их привели и Вы в своем сообщении > http://www.rsdn.ru/Forum/Message.aspx?mid=1698098&only=1
0. Не описывает всех возможных случаев.
1. Достаточно 0).
> Сожаления о том, что там не используется полиморфизм, эмоционально > понятны, но *профессионально* "не прокатывают".
Я говорю совершенно профессионально — ваш код взят из примеров "как не
надо писать" из учебников по ООП. Привести конкретные ссылки?
> C>Простите, я знаю что говорю. Тот же GDI рисует окружности _намного_ > C>быстрее кривых Безье. Хотя бы из-за того, что не всегда нормально > C>работает аппаратное ускорение для кривых Безье. > И что с того? К чему нам такое ускорение, если оно нам *не нужно*? > Повторюсь, глифы *TrueType* и *Type1* шрифтов используют кривые Безье.
А монитор использует пиксели. И что с того?
_КАК_ вы планируете делать осмысленые манипуляции с текстом (изменение
букв, например), представленном в виде набора кривых?
Вот у вас массив действий (вправо-вверх-на-10 вправо-вниз-на-4
влево-на-4 вправо-на-4 вправо-вниз-на-4). Что это такое?
> C>Вы знаете внутренности CD? > Мне приходилось проводить research на тему того, как лучше организовать > классы фигур для редактора векторной графики.
И где этот редактор?
> C>Еще раз. PS, PDF, bitmap — это конечные _представления_. Они не > C>предоставляют нормальной поддержки для манипуляции объектами. > 1) Bitmap упомянули Вы, а не я. > 2) Что касается PS и PDF, то тут Вы ошибаетесь.
Где? Как мне в PS прочитать и изменить текст, представленый в виде
набора кривых?
> C>_КАК_ вы определите какие точки принадлежат окружности? > Преобразую в полигон и проверю.
То есть у вас в редакторе будет еще и интеллектуальный распознаватель
фигур (вместе с OCR'ом и разумным демоном, вестимо).
Здравствуйте, Кирилл Лебедев, Вы писали:
C>>Ага, одним классом с баааальшим switch'ем. КЛ>Откуда Вы взяли огромный switch?
Решил тоже поучаствовать в вашем обсуждении/споре.
Подход Cyberax по поводу отдельного класса для каждой фигуры я понял. Но я пока не понял вашего подхода. Если есть 115 фигур, то в программе по любому должно быть 115 чего-то. Классов, веток в switch'е, функций, специализаций шаблона, разделов в xml-файле или ещё чего-то.
Так чего же вы предлагаете сделать 115?
Здравствуйте, remark, Вы писали:
R>Подход Cyberax по поводу отдельного класса для каждой фигуры я понял. Но я пока не понял вашего подхода. Если есть 115 фигур, то в программе по любому должно быть 115 чего-то. Классов, веток в switch'е, функций, специализаций шаблона, разделов в xml-файле или ещё чего-то. R>Так чего же вы предлагаете сделать 115?
Сама фигура (класс TFigure) хранит:
1) Описание контура (набор отрезков и кривых Безье). Такой набор обычно называют путем.
2) Толщину бордюра.
3) Цвет бордюра.
4) Цвет заливки.
При создании фигуры шаблон описания считывается из файла. При этом происходит преобразование координат из локальной (для фигуры) системы координат в систему координат документа.
Забыл добавить. Преимущество такого подхода — мы можем добавлять новые фигуры без изменения исходного кода и, следовательно, без перекомпиляции программы.
Кирилл Лебедев wrote: > Сама фигура (класс *TFigure*) хранит: > 1) Описание контура (набор отрезков и кривых Безье). Такой набор обычно > называют *путем*. > 2) Толщину бордюра. > 3) Цвет бордюра. > 4) Цвет заливки. > При создании фигуры шаблон описания считывается из файла. При этом > происходит преобразование координат из локальной (для фигуры) системы
А теперь представьте как в вашей модели изменять радиус окружности или
шрифт у надписи.
Кирилл Лебедев wrote: > C>А теперь представьте как в вашей модели изменять радиус окружности или > C>шрифт у надписи. > А вместо радиуса поменять диаметр *слабо*? > Рекомендую обратить внимание на ссылки (в них есть смысл)
А на простой вопрос не можете ответить? Каким образом мне осмысленно
представить _окружность_ и как сделать ее радиус доступным для
редактирования?
Здравствуйте, <Аноним>, Вы писали:
А>Здравствуйте, Cyberax, Вы писали:
C>>Кривые поддерживают принципиально больше возможностей управления. И при C>>этом принципиально медленнее рисуются. А>Действительно, это так. Однако на этих особенностях имеет смысл концентрироваться, если они имеют существенное значение для задачи. С точки зрения фигуры эти различия не важны.
Все дело в том что в вашем случае — если после дизайна потребуется, например, по причинам производительности, рефакторинг, то это будет очень непростой задачей.
В моем варианте (выделение в иерархию небольшого количества сущностей) рефакторинг будет вполне реален.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>С точки зрения "устройства, напичканные разными приборами" это, действительно, разные вещи. С точки зрения "устройства, которые летают" они одинаковы.
ИМХО:
"устройства, напичканные разными приборами" — классы
"устройства, которые летают" — одинаковые интерфейсы.
Опять приходим к полиморфизму
Здравствуйте, denisku, Вы писали:
D>"устройства, напичканные разными приборами" — классы D>"устройства, которые летают" — одинаковые интерфейсы. D>Опять приходим к полиморфизму
Классы и интерфейсы — это детали реализации. Если мне удобно, я могу реализовать летающее устройство в виде класса или описать для него интерфейс в виде интерфейса или абстрактного класса. В тоже время, если опять-таки это удобно, я могу реализовать летающее устройство в виде числа или функции. Например, никого не удивляет то, что в физике траектория полета ядра в безвоздушном пространстве описывается параболой. Зачем мне класс, если мне достаточно простой формулы?
Поэтому нельзя смешивать модель и реализацию. Даже у хорошей модели может быть плохая реализация. Но если модель плохая, то реализацию ничто не спасет.
Модель построения графических редакторов, предложенная во многих учебниках по ООП, и поддерживаемая Cyberax'ом, плохая. Плохая она не потому, что это мое такое частное мнение, а потому что она не сокращает количество сущностей. Все разнообразие форм предложено инкапсулировать в иерархии. От того и плодятся класс за классом. Если бы разработчики шрифтов или цветовых моделей следовали бы этой модели, то на каждый символ и на каждый цвет пришлось бы заводить по классу. (Подробнее о шрифтах я написал здесь http://www.rsdn.ru/Forum/Message.aspx?mid=1699457&only=1
А так все разнообразие контуров символов можно свести к комбинации отрезков и кривых, а все (или почти все) разнообразие цветов можно свести к комбинациям нескольких базовых цветов.
Насчет автофигур в WordXP — согласен, что там можно обойтись несколькими общими классами (а может даже и одним; по-моему так и сделано: Shape. Только тогда не ясно, как же дело обстоит с маркерами, которые можно мышкой двигать, — у них же разная функциональность). Конструктивно, беру на заметку.
Поддержка запросов действительно актуальна для редакторов и прочих приложений, манипулирующих большим количеством данных. Если утрированно, то получится что-то вроде "SQL для STL-контейнеров". Если браться за реализацию, то на диссертацию материала хватит.
И все-таки очень хотелось бы увидеть какой-нибудь небольшой пример на 3-й вариант, описанный Вами. Не реализацию, а интерфейс.
Здравствуйте, remark, Вы писали:
R>А что по поводу последнего вопроса: где и как размещать всё остальное, кроме рисования, что связано с фигурами?
Вы перечислили 7 вещей (ответы и вопросы по ходу):
1) Название.
Ответ: Как свойство фигуры. Содержится внутри файла.
2) Картинка.
Ответ: Либо как заливка (в расширенном виде фигура содержит не цвет, а имя заливки, которая описывается в файле точно так же, как фигура) или как картинка (ссылка на картинку присутствует в файле с описанием фигуры).
3) Способ размещения пользователем.
Вопрос: Что это такое? Нужны конкретные примеры.
4) Способ отмены рисования фигуры.
Ответ: Просто не создаем фигуру и все. Или я не понял вопроса.
5) Список свойств.
Вопрос: Какие свойства необходимы? Желательно, перечислите свойства для группы из не менее 5 фигур.
6) Радиус окружности.
Вопрос: Зачем он нужен?
7) Количество точек для полигона.
Ответ: Если имеется в виду аппроксимация кривых ломаными при выводе на устройство, то можно поступить двояко:
а) Параметр задается для устройства. В этом случае он задается не просто в виде количества точек, а в виде dpi.
б) Либо он задается для фигуры и является свойством фигуры.
Здравствуйте, Cyberax, Вы писали: C>А на простой вопрос не можете ответить? Каким образом мне осмысленно C>представить _окружность_ и как сделать ее радиус доступным для C>редактирования?
Я не Кирилл, но могу задать встречный вопрос: "А как редактировать радиус окружности в иерархии с классом на фигуру?"
Типа к каждой фигуре приписать ещё по вьюшке, которая позволяет как-то мышкой или ещё как менять радиус?
Может можно тоже как-то через какие-то хот-точки и формулы привязки этих точек к точкам пути задать?
Кроме того, пользователь всё равно вводит "начальные значения" для фигуры, которые каким-то способом потом конвертяться в кривые. Способ конверсии можно описать в виде формул в файле, а "начальные паоаметры" хранить с фигурой. Тогда при их редактировании фигуру можно просто перегенерить
Опять же, есть вопрос по семантике. Как менять радиус трансформированной окружности? Что вообще значит такая операция?
С уважением.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Опять же, есть вопрос по семантике. Как менять радиус трансформированной окружности? Что вообще значит такая операция?
Если окружность — отдельный объект, то радиус можно менять увеличением/уменьшением размеров прямоугольника элемента. Заодно эта окружность становится эллипсом, если высота не равна ширине.
В общем, предлагаемый подход очень похож на модель отрисовки в Visio, так, что там можно подсмотреть непонятные детали.
Erop wrote: > Я не Кирилл, но могу задать встречный вопрос: "А как редактировать > радиус окружности в иерархии с классом на фигуру?" > Типа к каждой фигуре приписать ещё по вьюшке, которая позволяет как-то > мышкой или ещё как менять радиус? > Может можно тоже как-то через какие-то хот-точки и формулы привязки этих > точек к точкам пути задать?
У меня есть специальный интерфейс ITrackerProvider — он позволяет
задавать custom'ный набор трекеров (в виде набора координат блоков).
За рисование трекеров и манипуляции с ними отвечает редактор, а не
фигура. Когда какой-то трекер перемещается — объект получает об этом
сигнал и может соответственно пересчитать свои части.
Ну и по умолчанию (если не реализован ITrackerProvider) объекты имеют
четыре трекера по углам, центральную точку и обработчик события
масштабирующий объект.
> Кроме того, пользователь всё равно вводит "начальные значения" для > фигуры, которые каким-то способом потом конвертяться в кривые. Способ > конверсии можно описать в виде формул в файле, а "начальные паоаметры" > хранить с фигурой. Тогда при их редактировании фигуру можно просто > перегенерить
Все красиво на бумаге... Декларативное описание получится достаточно
сложным для непростых фигур (см. Visio).
> Опять же, есть вопрос по семантике. Как менять радиус трансформированной > окружности? Что вообще значит такая операция?
Следует различать трансформации _фигуры_ и ее отображения.
Масштабирование, вращение, перекос — это просто изменения способа
отображения фигуры и не влияют на геометрию фигуры.
Изменением геометрии фигуры будет добавление нового луча к
четырехлучевой звезде, например.
Если можно пару вопросов к Вам не по Вашему спору с Кириллом Лебедевым а по организации вашего редактора
C>У меня есть специальный интерфейс ITrackerProvider — он позволяет C>задавать custom'ный набор трекеров (в виде набора координат блоков).
C>За рисование трекеров и манипуляции с ними отвечает редактор, а не C>фигура. Когда какой-то трекер перемещается — объект получает об этом C>сигнал и может соответственно пересчитать свои части.
1.Колличество и координаты трекеров все таки наверное определяет фигура?
...
C>Следует различать трансформации _фигуры_ и ее отображения. C>Масштабирование, вращение, перекос — это просто изменения способа C>отображения фигуры и не влияют на геометрию фигуры.
2.То есть координаты самих графических объектов не изменяются а пересчитываеются при каждом отображении?
3.Вы реально применяете объекты как экземпляры классов или как то иначе храните данные объектов?
Не сильно ли грузиться память при наличии скажем хотябы 10000 объектов?
Любопытство не праздное — я сам занимаюсь разработкой подобного редактора. Обмен опытом как никак
Здравствуйте, Белая Крыса, Вы писали:
БК>Здравствуйте, LuciferMoscow, Вы писали:
LM>>Здравствуйте, Дядюшка Че, Вы писали: LM>><skipped> LM>>Bridge? БК>А может Visitor?
Визитор тут уже обсуждался. Он явно для другого предназначен.
Здравствуйте, Кирилл Лебедев, Вы писали:
КЛ>Здравствуйте, remark, Вы писали:
R>>А что по поводу последнего вопроса: где и как размещать всё остальное, кроме рисования, что связано с фигурами?
КЛ>Вы перечислили 7 вещей (ответы и вопросы по ходу):
КЛ>1) Название.
КЛ>Ответ: Как свойство фигуры. Содержится внутри файла.
Согласен
КЛ>2) Картинка.
КЛ>Ответ: Либо как заливка (в расширенном виде фигура содержит не цвет, а имя заливки, которая описывается в файле точно так же, как фигура) или как картинка (ссылка на картинку присутствует в файле с описанием фигуры).
Согласен
КЛ>3) Способ размещения пользователем.
КЛ>Вопрос: Что это такое? Нужны конкретные примеры.
Когда пользователь рисует линию, то он может:
1. указать мышкой один конец линии и другой
2. если нажат shift, указать мышкой один конец линии и другой. при этом линия должна рисоваться под 45 градусов
3. если нажат ctrl, указать мышкой середину линии и конец линии
Когда пользователь рисует окружность, то он может:
1. указать центр и точку на контуре
2. если нажат shift, указать 3 точки на контуре
3. если нажат ctrl, указать центр и точку на контуре, при этом радиус должен быть дискрутный, т.е. округляться
Когда пользователь рисует полигон, то он может:
1. указывать тогки полигона, потом нажать правую-кнопку и контрур замыкается
Т.е. с каждой фигурой сопоставляются некоторые "удобные" способы рисования, как следствие присущие именно этой фигуре.
Я пока слабо представляю как это просто вынести во внешний файл. Без кода для каждой фигуры.
КЛ>4) Способ отмены рисования фигуры.
КЛ>Ответ: Просто не создаем фигуру и все. Или я не понял вопроса.
Тут я имею в виду, что если рисунок не векторный, а битмап. Для возможности отмены рисования фигуры может быть расточительно хранить изображение, которок было до рисования фигуры, что бы его просто восстановить. Хотелось бы с фигурой по возможности сопоставить некий кусок кода, который по параметрам фигуры может её "стереть". Т.о. для возможности отмены не надо хранить пару байт, а не мегабайт.
КЛ>5) Список свойств.
КЛ>Вопрос: Какие свойства необходимы? Желательно, перечислите свойства для группы из не менее 5 фигур.
Ну допустим тебе в ТЗ добавляют требование, что для фигур надо выводить некорую статистику: для полигона количество точек, для круга точку центра и радиус, для ешё чего-то ещё что-то.
При этом требования меняются: вначале говорят, что для окружности надо выводить, что она состоит из 3 точек (т.к. задаётся по 3 точкам), потом, что из 2 (центр+точка на контуре), потом, что для неё вообще не надо выводить, из скольких точек она состоит.
КЛ>6) Радиус окружности.
КЛ>Вопрос: Зачем он нужен?
Вполне законно, если пользователь нарисовал окружность радиусом 10, а потом захотел изменить радиус на 15, чтобы он мог просто поменять одно число с 10 на 15.
КЛ>7) Количество точек для полигона.
КЛ>Ответ: Если имеется в виду аппроксимация кривых ломаными при выводе на устройство, то можно поступить двояко:
КЛ>а) Параметр задается для устройства. В этом случае он задается не просто в виде количества точек, а в виде dpi. КЛ>б) Либо он задается для фигуры и является свойством фигуры.
Или, например, если взять редактор UML, то там с каждой "фигурой" надо сопоставлять набор свойств+диалог для их редактирования
Вобщем мысль такая, что во многих редакторах, что бы они были удобные и полезные, надо с каждым инструментом сопоставлять некий объём кода.
По-моему, вы тут в пример приводили post-script: если программа для его вывода/интерпретации может быть действительно простой и обходиться 3 типами примитивов, то это не значит, что программа для его вёрстки должна быть такой же простой и исеть тулбар с 3 инструментами.
Здравствуйте, Cyberax, Вы писали: C>А на простой вопрос не можете ответить? Каким образом мне осмысленно C>представить _окружность_ и как сделать ее радиус доступным для C>редактирования?
В принципе, эту задачу вполге можно реализовать. Правда пример Кирилла с постскриптом некорректен — постскрип — это read only формат, он не годится для "осмысленного" редактирования высокого уровня . Как это можно решить — предоставить набор базовых фигур (не обязательно самых базовых, как в примере Кирилла), из которых можно быстро и удобноо составить остальные и скриптовый API, который позволит добавлять новые фигуры и описывать их свойства (контрольные точки, параметры и т.д).
Пример описания такой фигуры из головы, с использованием синтаксиса скриптового языкка lua:
shape{
name="rectangle with text",
-- Control points
controls {
text_position = control {
-- Might be also initialized externally inside shape.on_init
on_init = function()
self.x = shape.width/2
self.y = shape.height/2
end
},
},
-- Graphical elements
elements {
lines = group{ line{}, line{}, line{}, line{} },
text = text{},
},
-- Modifiable properties
properties {
text = string_param{ on_change = function(new_text) self.elements.text.set_text( new_text ) end }
},
-- Behaviour
on_init = function()
resize(100,100)
end,
on_resize = function()
self.elements.text.x = self.controls.text_position.x
self.elements.text.y = self.controls.text_position.y
self.elements.lines[0].x = 0
self.elements.lines[0].y = 0
self.elements.lines[1].x = shape.width
self.elements.lines[1].y = 0
-- etc
end
}
И несмотря на кажущуюся громоздкость, такое достаточно легко реализовать.
"To protect people you must slay people. To let people live you must let people die. This is the true teaching of the sword."
-Seijuro Hiko, "Rurouni Kensin"