У меня тут есть немного студентов на нашей базовой кафедре, которых надо научить плюсам в свободное от работы время. Пока всё шло хорошо, но скоро надо будет объяснить им наследование. Пока план примерно такой:
Рассказать про открытое наследование интерфейса. Показать как писать чисто абстрактные классы. Рассказать про чисто виртуальные функции.
Показать как удобно объекты с одним интерфейсом втыкаются в обобщенный код
Немного блабла про is-a и LSP
Упомянуть про наследование реализации, рассказать про просто виртуальные функции, статический и динамический тип, показать примеры когда это полезно и когда вредно. Особо остановиться на виртуальных деструкторах.
Упомянуть про закрытое и защищённое наследование. Объяснить что закрытое это такая композиция (и поэтому лучше делать явную композицию), а защищённое вообще никто не знает зачем нужно.
Рассказать про RTTI и dynamic_cast.
И дальше отправиться в космос -- множественное наследование, ромбовидные схемы, виртуальные базовые классы, etc.
Но весь этот план требует некоего сквозного примера для которого проводить объяснения. И вот тут у меня затык.
Все книги по C++ которые объясняют наследование вызывают желание убить себе лицо рукой. Вызывали когда я был на втором курсе, вызывают сейчас много лет спустя. Даже дедушка Строструп не уберёгся и унаследовал класс Manger от класса Employer, стыд и унижение. Варианты наследовать котят от собачек, груши от фруктов, кружочки от квадратиков и т.п. не рассматриваются, мне будет стыдно рассказывать, им будет стыдно слушать. Когда я совсем состарюсь пойду воспитателем в детский сад, там мне это очень пригодится, а пока ну нафиг.
Нахожусь в активном поиске нормального вменяемого примера наследования. Пока что придумал такую идею -- наследовать от "графа вообще" его частные случаи -- CFG, DAG, дерево. Близко к моей основной теме (оптимизирующие компиляторы), можно что-то рассказать дополнительно.
Но хотелось бы послушать мнение народа. Как бы вы объясняли наследование?
Здравствуйте, Tilir, Вы писали:
T> немного студентов
T> Варианты наследовать котят от собачек, груши от фруктов, кружочки от квадратиков и т.п. не рассматриваются, мне будет стыдно рассказывать, им будет стыдно слушать.
Про кошечек и собачек может быть и стыдно, но студенты наверняка играют в игры. Вот на примере иерархии игровых объектов стратегии и можно объяснить наследование. Например, базовый класс Unit. От него наследуются всякие там танки, машины, пехота. У каждого из этих юнитов своё поведение. Подошла армия к реке — плавающие юниты двинулись вплавь, неплавающие в обход, искать брод или мост. Подошли к болоту — гусеничные ломятся напролом, колёсные в объезд. Это поведение реализовано в перегруженном виртуальном методе Move. Студентам должно быть интересно.
T>Все книги по C++ которые объясняют наследование вызывают желание убить себе лицо рукой. Вызывали когда я был на втором курсе, вызывают сейчас много лет спустя. Даже дедушка Строструп не уберёгся и унаследовал класс Manger от класса Employer, стыд и унижение. Варианты наследовать котят от собачек, груши от фруктов, кружочки от квадратиков и т.п. не рассматриваются, мне будет стыдно рассказывать, им будет стыдно слушать. Когда я совсем состарюсь пойду воспитателем в детский сад, там мне это очень пригодится, а пока ну нафиг.
T>Нахожусь в активном поиске нормального вменяемого примера наследования. Пока что придумал такую идею -- наследовать от "графа вообще" его частные случаи -- CFG, DAG, дерево. Близко к моей основной теме (оптимизирующие компиляторы), можно что-то рассказать дополнительно.
О господи!
Студенты не знают еще толком С++ (так я понял). Они еще не очень ориентируются в синтаксисе и в простейших приемах. А им предлагается взять в качестве основы граф (не самую простую структуру, вообще-то), сделать от него наследником дерево (которое совсем не обязательно реализовать как граф), рассказать что-то про оптимизирующие компиляторы , которые , видите ли, являются основной любовью лектора, и поэтому их надо сюда обязательно приплести. Тихий ужас.
Собачек от кошечек наследовать не надо. А вот груши от фруктов или прямоугольники от абстрактной фигуры — это и есть самое простое решение. На этом примере можно показать основные принципы и идеи. А при показе основных принципов нужно использовать как можно более простой пример, дабы не затемнять эти принципы вопросами, не имеющими к ним прямого отношения.
Освоят основные идеи — тогда, пожалуйста, переходите хоть к графам, хоть к деревьям и т.п.
Здравствуйте, Tilir, Вы писали:
T>Нетривиальная задача в моём понимании это не что-то такое, что сложно запрограммировать, а что-то, что нужно запрограммировать, поскольку это неоправданно долго делается без привлечения ЭВМ и т.п.
Это замечательно, но какое отношение это имеет к ООП ?
T>Что конкретно делать с этим списком? Пример должен иметь осмысленную постановку и осмысленное применение. "Перемножить матрицы и найти определитель" -- бессмысленная задача. "Подсчитать токи в заданной цепи" -- осмысленная задача.
Демонстрировать приемы и понятия ООП. При изучении ООП надо именно это делать, не затемняя эту тему чем бы то ни было посторонним.
Если конкретно — показать, как работает виртуальная , скажем, print, почему происходит memory leak при невиртуальном деструкторе, и т.д.
T>Иерархия небесных тел позволяет прикинуть решение задачи трёх тел, вывести в файл их траектории, посмотреть их с помощью gnuplot. Что делать с иерархией животных?
Ради всего святого, объясни, какое отношение задача трех тел имеет к ООП ? Почему учить ООП надо на примере задачи трех тел — не понимаю. Ее на Фортране можно написать, где ООП и в помине не было. И ничем не хуже будет рассчитана траектория.
Здравствуйте, neFormal, Вы писали:
T>>мне будет стыдно рассказывать, им будет стыдно слушать. Когда я совсем состарюсь пойду воспитателем в детский сад, там мне это очень пригодится, а пока ну нафиг.
F>тебе нельзя преподавать.
Я бы несколько мягче высказался. Нельзя — это уж слишком.
Ситуация же вот какая. Есть (положим) хороший специалист в своей области, и вот он берется преподавать. К сожалению, многие, попавшие в это положение, склонны пренебрегать существующим опытом, и, в уверенности, что коль они хорошие специалисты в своем деле, то уж научить они без труда смогут. При этом не учитывается разница между собственным уровнем и уровнем обучаемых.
Результат — либо по ходу действия придется переориентироваться и действовать в применении к реальности, либо человек уходит из этой области.
Здравствуйте, Tilir, Вы писали:
T>Но весь этот план требует некоего сквозного примера для которого проводить объяснения. И вот тут у меня затык.
T>Все книги по C++ которые объясняют наследование вызывают желание убить себе лицо рукой.
Молодец! Никого не слушай — всё правильно думаешь.
Я бы начал вот с чего:
1. Объяснил бы концепцию полиморфизма вообще. На примере, сначала, одной функции. Показываем, как можно добиться интересных эффектов путём параметризации алгоритма функцией. Объясняем, что это в жизни ещё очень пригодится, и что одного этого приёма хватает в очень многих случаях.
Пример — поиск в массиве.
Развитие примера — поиск в дереве. Пока что даже нет нужды вводить какие-либо классы; дерево вполне может быть plain struct. Главная идея — объяснить, зачем вообще разделять код итерирования по дереву и код предиката: на переборе массива неочевидно, что find(array, less_or_equal_to_5()) лучше, чем for(int i=0;i<arraayLen; i++) if array[i]<=5 { res = i; break;}.
Здесь как бы уже возникает дихотомия между интерфейсом (сигнатурой функции) и реализацией — реальной функцией.
2. Объяснил бы тот момент, когда придётся отказаться от функции, и перейти к объектам. Например, хранение состояния — например, агрегирующая функция. Типа как нам подсчитать сумму/среднее/минимум/максимум элементов в массиве/дереве/и т.п?
Теперь у нас есть интерфейс IAggregator (c одним методом), и возможность реализовать разные алгоритмы агрегирования.
3. Расширил бы пример с введением интерфейса из нескольких функций, которые должны работать согласованно. Например, с умножением матриц — если мы берём пару из (*, +), то получаем классическое перемножение матриц. А если берём пару из (+, min), то мы находим кратчашие пути при умножении матрицы смежности самой на себя.
4. В качестве домашнего задания предложил бы реализовать класс "калькулятора" — т.е. штуки, которая умеет вести вычисления с плавающей запятой. При этом надо сделать две реализации — одну "быструю", с табличной реализацией тригонометрии и загрублённой арифметикой с бит-шифтами, другую "точную", через сопроцессор. И сравнить результаты решения одной и той же численной задачи с использованием двух таких калькуляторов.
5. Напоследок бы сказал, что ситуация, когда используется более одного уровня наследования — экзотика, которая скорее всего им в жизни потребуется очень-очень нескоро. И что когда кажется, что есть повод такую иерархию ввести (например, отнаследовавшись от класса, реализующего интерфейс), надо сначала посмотреть, нет ли способа решить задачу без наследования.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Tilir, Вы писали:
T>Здравствуйте, Miroff, Вы писали:
M>>Что-нибудь рассчетное, например, орбиты планет. Важно не просто наследование, в преимущества которые оно дает, в частности полиморфизм.
T>А вы не могли бы подробней про орбиты планет? Что наследуется, какие преимущества даёт?
Здравствуйте, Tilir, Вы писали:
T>Все книги по C++ которые объясняют наследование вызывают желание убить себе лицо рукой. Вызывали когда я был на втором курсе, вызывают сейчас много лет спустя. Даже дедушка Строструп не уберёгся и унаследовал класс Manger от класса Employer, стыд и унижение. Варианты наследовать котят от собачек, груши от фруктов, кружочки от квадратиков и т.п. не рассматриваются, мне будет стыдно рассказывать, им будет стыдно слушать. Когда я совсем состарюсь пойду воспитателем в детский сад, там мне это очень пригодится, а пока ну нафиг.
Не по теме, но, честно говоря, лучше бы студентам рассказывали более подробно про функциональную декомпозицию. Имхо, это сейчас новички не умеют делать от слова совсем. Сразу начинают лепить куда придётся эти наследования, инкапсуляции и прочие полиморфизмы, в то время как всё это стоит применять уже после проведения декомпозиции. Похоже и у дедушки Страуструпа та же проблема...
Здравствуйте, Tilir, Вы писали:
T>Но хотелось бы послушать мнение народа. Как бы вы объясняли наследование?
То, что пример найти не удается, это как бы намекает на то, что Вы ищете среди примеров на наследование реализации.
Если говорить о наследовании интерфейса, пример будет легче найти, например какой-нибудь сервис, одна реализация лезет в БД, другая работает в памяти,
а интерфейс у них один и тот же. Это сплошь и рядом используется для тестирования.
Вообще я бы рассказал немного, как это сделано в других языках. Хотя бы в том же C#, там ключевое слово interface есть.
Боюсь что многочисленные тонкости C++ отвлекут внимание студентов от сути концепций...
Вообще я бы больше акцентировал внимание на виртуальных (и чисто виртуальных) методах, студенты не понимают что это и зачем нужно.
Здравствуйте, Abyx, Вы писали:
A>y STLа классы называются meow и purr, при этом он офигенно крут, и объясняет вещи нормально.
Лававеевские подкасты это плюсовое безумие, рассчитанное на расширение кругозора профессионалов.
template<class U>
struct purr < U, enable_if<std::is_integral<U>::value > > {};
Хм. Ну да. Purr.
Близкий аналог -- известная книга Дона Бокса про COM Internals, где кокласс Горилла маршалит экземпляр банана. Меня всегда несколько раздражали эти ужимки и прыжки. Да, профи через кисок продерётся и мессадж поймёт. Но 9/10 дропнут на середине. Ибо от кисок -- тошнит. Программист всё-таки занят делом, по большей части -- серьёзным и полезным.
Здравствуйте, UA, Вы писали:
T>>Но хотелось бы послушать мнение народа. Как бы вы объясняли наследование?
UA>На примере какой нибудь GUI библиотеки.
Начинающему очень рано в это влезать.
Тем более, что "GUI библиотеки" обычно переполнены тонкостями конкретной технической реализации. Что снижает их ценность в данном случае.
Здравствуйте, Tilir, Вы писали:
T>мне будет стыдно рассказывать, им будет стыдно слушать. Когда я совсем состарюсь пойду воспитателем в детский сад, там мне это очень пригодится, а пока ну нафиг.
Здравствуйте, Tilir, Вы писали:
F>>тебе нельзя преподавать. T>Неизбежный оверхед задавания вопросов на любом русскоязычном форуме: тебе помимо всего прочего рассказывают о том кто ты и зачем нужен. Я всё ждал -- кто же, кто же.
Здравствуйте, Tilir, Вы писали:
T>Но весь этот план требует некоего сквозного примера для которого проводить объяснения. И вот тут у меня затык.
Вы не знаете, для чего нужно наследование, а беретесь объяснять другим. Поэтому я всегда считал, что препод должен быть ветераном промышленности.
Если я не прав, откройте свой код и возьмите оттуда столько примеров, сколько надо.
***
Оффтопик. Главное, что я бы про наследование рассказал — есть места, где оно нужно, поскольку облегчает чтение, а есть места, куда его втыкают либо по глупости, либо из-за ограниченности языка. Пример первого — любая оконная библиотека. Пример второго — boost::operators. Обожаю на диаграмме наблюдать [boost::operators] -> [FileInfo] только потому, что кто-то коллекцию FileInfo решил отсортировать. Не надо так делать. Презренные макросы и то лучше.
Здравствуйте, Pavel Dvorkin, Вы писали:
F>>потому что это сюси-пуси для девочек. F>>в смысле, комплексы. PD>Комплексы у того, кто так считает ? Его проблемы.
Здравствуйте, Tilir, Вы писали:
T>Здравствуйте, UA, Вы писали:
UA>>На примере какой нибудь GUI библиотеки.
T>Возможно имеет смысл -- отнаследовать кнопку с прогрессбаром от просто кнопки... должно быть увлекательно и для студентов наглядно. Но тут меня останавливает вопрос какую библиотеку взять за пример няшного ООП? Я в жизни использовал три GUI-библиотеки, ещё когда что-то кодил под винду. Это VCL, MFC и WTL. Все три с точки зрения правильного плюсового ООП -- ад из костылей, тяжелых наркотиков и легаси-кода. К моему великому счастью, последние четыре года я к GUI не прикасался. Я слышал, в QT всё несколько получше, но никогда не смотрел внимательней.
Любая GUI библиотека завязана на технические тонкости (зависящие прежде всего от особенностей ОС и их реализации в этой библиотеке).
Посему, изучение основ ООП на ней — далеко не самый правильный вариант.
Здесь уже достаточно много написали, насчет примеров.
Добавлю только, что на мой взгляд, препод должен дать именно пример на понимание принципов ООП (в данном случае — наследования).
Добиваться, чтобы этот пример делал какую-то сложную функцию, кроме как демонстрация наследования — нет смысла,
так как это только распыляет внимание студента
P.S. Так, например, студент может сделать хороший расчет электрической цепи, правильно применив законы Ома, Кирхгофа и т.д.
При этом, наследовав класс "лампочка", от класса "аккумуляторная батарея", на том основании, что лампочка питается от...
Я полагаю, что речь тут не об этом. Не о том, "как изгалиться", чтоб было под ООП
Речь о том, чтобы показать студенту реализацию принципов ООП, сделав это на доступном примере.
ИМХО тут надо что-то достаточно абстрактное и в то же время понятное.
А вот когда человек поймет суть ОО проектирования, тогда уже переходить к конкретике, например в виде тех же GUI библиотек.
UA>P.S. Я призываю использовать для обучения "не нативные" gui типа Qt.
Возможно.
К сожалению, я с Qt не работал, посему мне трудно тут как-то судить.
Здравствуйте, mefrill, Вы писали:
M>Не понимаю, чем геометрические фигуры не угодили? Слишком просто что-ли, почему стыдно-то? Есть хорошие примеры реальных таксономий, классификация Линнея, например. Можно попробовать реализовать классификацию, которая таксономией не является, ту же таблицу периодических элементов.
Допустим вы построили иерархию геометрических фигур. Или классификацию Линнея. Что дальше вы будете делать с этой иерархией классов? Какие конкретно задачи вам поможет решить наследование, какие без него были бы неоправданно сложны?
Если ответ "никакие", значит это контрпример -- пример ситуации, когда наследование избыточно и поэтому не нужно.
T>Это всё есть. Но это не о наследовании, а чуть дальше. На механике (очень тонкой, включая даже их реализацию на чистом C) работы виртуальных функций, я останавливаюсь очень подробно. Так же и виртуальные деструкторы.
Я бы не стал на начальном этапе говорить о том, как это реализуется (на чистом С). Тут все вообще-то на грани С и ассемблера, а они его знают ? Хотя как устроена vtable я им рассказываю.
PD>>Ради всего святого, объясни, какое отношение задача трех тел имеет к ООП ? Почему учить ООП надо на примере задачи трех тел — не понимаю. Ее на Фортране можно написать, где ООП и в помине не было. И ничем не хуже будет рассчитана траектория.
T>Потому что мы учим не ООП а C++. C++ язык мультипарадигменный.
Если речь идет о той части С++, где классы — то это ООП. Остальные элементы мы в этом треде не обсуждали.
>И если оказывается, что задача трёх тел проще/естественнее считается без ООП, это очень хорошо.
Это ни хорошо, ни плохо. Это просто перпендикулярно. ООП — это ООП и его принципы, а задача трех тел — это физика и математика.
>Значит студенты должны это дедуцировать и решить её на C++ без ООП.
Совсем не обязательно. Можно написать библиотеку , скажем, линейной алгебры без классов, а можно и с классами. И выбор тут совсем не очевиден, it depends.
>Поэтому я так критичен к примеру наследования. Мне нужно не какое угодно наследование. Мне нужно наследование только такое, чтобы было очевидно, что оно нужно и очевидно зачем оно нужно.
Я привел в другом месте список методов, которые можно наследовать от базового класса Unit. ИМХО одного этого списка вполне достаточно, чтобы показать, почему наследование от Unit имеет смысл и его стоит использовать, и почему без него будет сложнее и хуже.
Здравствуйте, Tilir, Вы писали: T>Допустим вы построили иерархию геометрических фигур. Или классификацию Линнея. Что дальше вы будете делать с этой иерархией классов? Какие конкретно задачи вам поможет решить наследование, какие без него были бы неоправданно сложны? T>Если ответ "никакие", значит это контрпример -- пример ситуации, когда наследование избыточно и поэтому не нужно.
Эту иерархию классов можно использовать в написании простенького графического редактора.
На примере фигур можно показать, что код с наследованием получается проще и понятнее:
1. Можно нарисовать все фигуры, пробежав по списку указателей std::list<Figure *> и вызывая виртуальный метод draw().
2. Можно найти сумму площадей в пару строчек.
3. Можно сохранить данные всех фигур в файл, заодно можно рассмотреть проблему с чтением из файла и её решение в виде фабричного метода.
А перед этим можно показать пример, где часть этих задач решается без наследования, с помощью длинных switch(type) или указателей на функции.
И добить добавлением нового типа фигуры. С наследованием будет добавлен один класс (изменения локализированы), а без наследования изменения будут размазаны по всей программе.
Здравствуйте, Tilir, Вы писали: T>У меня тут есть немного студентов на нашей базовой кафедре, которых надо научить плюсам в свободное от работы время. Пока всё шло хорошо, но скоро надо будет объяснить им наследование. Пока план примерно такой:
Наследование идет от АТД (Абстрактный Тип Данных), под наследование можно загнать все что угодно.
Например мебель. Бизнес область домашний интерьер под ключ. Визуализация транспортировка и сборка.
Есть базовый интерфейс мебель. Например компания по обстановке мебели в квартире.
ЭлементМебели{атрибуты: размеры, вес, хрупкий, занимаемый объем в пространстве} абстратный
стол|стул|шкаф|лежак — наследники ЭлементМебели тоже абстрактные типы, добавляют свои атрибуты — специфицируют базовый интерфейс.
Каждый элемент можно расширить своим списком свойств у стола количество персон, у стула например мягкий-жесткий материал сидения, у шкафа количество дверей, наличие антресоли, количество полок. Лежак: диван, кровать, уголок. Уголок может состоять из дивана и кресла (композит) теоретически можно собирать как конструктор, переопределением может служить количество персон диван:2 кресло:1, в сложенном состоянии могут сесть не (1+2)=3, а 4 персоны. Множественное наследование это парта — и стол и стул конструктивно неделимо. Дверь/ящик/матрас не является элементом мебели. Например шкаф-дверь это композиция(шкаф без двери уже хз вроде как не шкаф), а шкаф-полка агрегация(т.к. количество полок можно варьировать ну и без полок шкафы тож бывают).
Добавляем динамики двери можно пооткрывать, ящики выдвигать, диван раскладывать. При действии совершаемой над мебелью меняется объем занимаемого пространства. Если добавить декораторы к мебели: колесики или светодиодная подсветка, то мебель можно покатать и включить свет.
Контейнер мебели комната. Если надо показать именно виртуализацию, то можно над этими объектами совершать различные манипуляции по разному. Например подготовка к показу: закрыть все двери и задвинуть все ящики. Или например подготовка ко сну: включить все что включается, разложить диван, закрыть все дверцы шкафов и задвинуть ящики.
Здравствуйте, Dufrenite, Вы писали:
D>Я понял в чём дело. У меня так называемый data-oriented подход. Главное правильно разместить данные, а алгоритмы обхода делаются по мере надобности. Все другие способы построения иерархий классов, в частности с привязкой к реальному миру приводят к эпик-фейлам.
Вот тут опять есть нюансы. На моей памяти, попытка выстроить иерархию наследования на основе данных как раз и приводит к эпик фейлам.
Классический пример — квадрат/прямоугольник/эллипс/круг. Вроде бы и про данные всё понятно, а иерархия никак не вытанцовывается.
Единственный понятный мне способ ОО-проектирования — это плясать от поведения.
Ну, то есть решаем задачу в стиле "ок, вот у нас есть идеальный "решатель задачи" для каждой сформулированной нами задачи. Давайте посмотрим, можно ли выстроить какую-то классификацию этих решателей; можно ли выделить из их ответственностей какие-то фрагменты, которые сделают общее решение проще и понятнее".
Важный момент — речь идёт про "решатель задачи", а не просто про "класс для представления сущности из предметной области".
Например, если у нас речь шла про геометрические фигуры, то "задача" — это типа "нарисовать на экране прямоугольник", и решатель, соотвественно — это "рисователь прямоугольников на экране", а вовсе не "класс Прямоугольник с методом Draw".
В результате такой декомпозиции мы быстро получим "рисователя отрезков на экране" и "рисователя ломаных на экране", а рисователи прямоугольников, треугольников, квадратов и прочих фигур исчезнут за ненадобностью.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Dufrenite, Вы писали: D>Даже не знаю что ответить. Чем вас паттерны настораживают?
Тем же самым — паттерн должен вводиться не как самоцель, а как решение какой-то задачи. Причём так, что "без паттерна" решение получается хуже по объективным характеристикам.
D>Вы всю логику хотите в 2 класса поместить? Видал я классы по 5000 строк, но уверяю что это неверный путь.
В данном случае двух классов вполне достаточно. В вашем решении банально больше строк — то есть больше объём работы по тестированию, больше шансов сделать ошиююку.
D>Логически подумайте. Чем больше разнообразных задач вы навесите на иерархию, тем больше в ней будет нестыковок, костылей и компромиссов.
Не вижу никакой логики, равно как и причин, по которым вы предлагаете мне навесить на единую иерархию множество разнообразных задач.
Проектирование от поведения совершенно не об этом.
D>Идеально разнести задачи по разным классам или иерархиям. В этом случае понимание кода сильно упрощается.
S>>А какой код и сколько нам потребуется в ShapeDrawer? Я же правильно понял, что в нём будет по методу на каждого потомка Shape?
D>Верно. Всё, что связано с рисованием будет в нём.
S>>Простите, но такой код хорош только для списания затрат по time spent basis. S>>Для реальной работы надо минимизировать усилия. В вашем коде стоимость добавления новой фигуры — запредельна. D>Добавление одного метода из 10 строк для вас запредельная сложность?
С чего это "одного"? В вашем решении мне нужно:
1. Добавить новый класс фигуры. Сколько в нём будет строк кода?
2. Добавить новый метод в IShapeVisitor
3. Добавить новый метод в каждую реализацию IShapeVisitor. В вашем примере их две.
В моём решении единственное, что потребуется добавить во весь проект — это как раз один метод.
Примерно вот так:
public static Path CreateRightAngleTriangle(Pen pen, Point corner, int height, int width)
{
return Path.CreateClosedPath(pen, corner, corner + new Offset(0, -height), corner + new Offset(width, 0));
}
Всё. А теперь приведите мне тот код, который потребуется написать в вашем проекте, чтобы заработали прямоугольные треугольники.
Если я попрошу вас научить вашу программу поворачивать Shape на произвольный угол, то вы мгновенно утонете в коде. А у меня добавится ровно один метод Path RotatePath(Path original, double angle). Никакого полиморфизма, никакого размазывания изменений по всем файлам проекта, минимум риска сделать ошибку.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
T>Hi,
T>У меня тут есть немного студентов на нашей базовой кафедре, которых надо научить плюсам в свободное от работы время. Пока всё шло хорошо, но скоро надо будет объяснить им наследование. Пока план примерно такой:
T>
T>Рассказать про открытое наследование интерфейса. Показать как писать чисто абстрактные классы. Рассказать про чисто виртуальные функции. T>Показать как удобно объекты с одним интерфейсом втыкаются в обобщенный код T>Немного блабла про is-a и LSP T>Упомянуть про наследование реализации, рассказать про просто виртуальные функции, статический и динамический тип, показать примеры когда это полезно и когда вредно. Особо остановиться на виртуальных деструкторах. T>Упомянуть про закрытое и защищённое наследование. Объяснить что закрытое это такая композиция (и поэтому лучше делать явную композицию), а защищённое вообще никто не знает зачем нужно. T>Рассказать про RTTI и dynamic_cast. T>И дальше отправиться в космос -- множественное наследование, ромбовидные схемы, виртуальные базовые классы, etc. T>
T>Но весь этот план требует некоего сквозного примера для которого проводить объяснения. И вот тут у меня затык.
T>Все книги по C++ которые объясняют наследование вызывают желание убить себе лицо рукой. Вызывали когда я был на втором курсе, вызывают сейчас много лет спустя. Даже дедушка Строструп не уберёгся и унаследовал класс Manger от класса Employer, стыд и унижение. Варианты наследовать котят от собачек, груши от фруктов, кружочки от квадратиков и т.п. не рассматриваются, мне будет стыдно рассказывать, им будет стыдно слушать. Когда я совсем состарюсь пойду воспитателем в детский сад, там мне это очень пригодится, а пока ну нафиг.
T>Нахожусь в активном поиске нормального вменяемого примера наследования. Пока что придумал такую идею -- наследовать от "графа вообще" его частные случаи -- CFG, DAG, дерево. Близко к моей основной теме (оптимизирующие компиляторы), можно что-то рассказать дополнительно.
T>Но хотелось бы послушать мнение народа. Как бы вы объясняли наследование?
T>--- T>With best regards, Konstantin
В Java очень хорошо наследование показано стандартными коллекциями: List, Map, Set ... etc
А вот например С++ виртуальное наследование, я до сих пор не видел прикладных задач — где это нужно.
Здравствуйте, Miroff, Вы писали:
M>Что-нибудь рассчетное, например, орбиты планет. Важно не просто наследование, в преимущества которые оно дает, в частности полиморфизм.
А вы не могли бы подробней про орбиты планет? Что наследуется, какие преимущества даёт?
Здравствуйте, Tilir, Вы писали: T>Но хотелось бы послушать мнение народа. Как бы вы объясняли наследование?
В качестве намека.
При реализации интерпретатора виртуальной машины систему команд удобно представлять иерархией наследования с абстрактным классом во главе.
И здесь же — паттерн Команда можно объяснять. И функтор — тоже.
В нашей среде Semantic операторы учебного языка построены в иерархию наследования на основе паттерна Наблюдатель.
Прога представляет собой многоуровневый список списков — семантическое дерево. Один оператор — это узел дерева.
Обход дерева — это Визитор.
Изменения в каком-нить узле — Наблюдатель работает.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Miroff, Вы писали:
M>Коллекции лучше не трогать, а то придется про ко- и контрвариантность рассказывать.
А нафига так преподавать?
Для меня на практике, наследование — это формальность — детали реализации.
Тот же List у нас в массиве хранится (ArrayList) либо на удаленном сервере (своя реализация AbstactCollection — которая по сети данные гоняет), либо в базе данных (spring / jpa).
Ну да для конечного пользователя — это просто List — интерфейс.
Здравствуйте, vpchelko, Вы писали:
V>Здравствуйте, Miroff, Вы писали:
M>>Коллекции лучше не трогать, а то придется про ко- и контрвариантность рассказывать.
V>А нафига так преподавать?
V>Для меня на практике, наследование — это формальность — детали реализации.
V>Тот же List у нас в массиве хранится (ArrayList) либо на удаленном сервере (своя реализация AbstactCollection — которая по сети данные гоняет), либо в базе данных (spring / jpa).
V>Ну да для конечного пользователя — это просто List — интерфейс.
А там можно перейти к прелестям Java — Proxy Injection. Которые в рантайме строят нужный нам (конфигу) объект.
Здравствуйте, koodeer, Вы писали:
K>Про кошечек и собачек может быть и стыдно, но студенты наверняка играют в игры. Вот на примере иерархии игровых объектов стратегии и можно объяснить наследование. Например, базовый класс Unit. От него наследуются всякие там танки, машины, пехота. У каждого из этих юнитов своё поведение. Подошла армия к реке — плавающие юниты двинулись вплавь, неплавающие в обход, искать брод или мост. Подошли к болоту — гусеничные ломятся напролом, колёсные в объезд. Это поведение реализовано в перегруженном виртуальном методе Move. Студентам должно быть интересно.
Присоединюсь к предложению рассказывать на примере RTS-игры.
Я именно так и объяснял когда-то стажёрам, нафига нужны виртуальные функции. Когда выделяем мышкой группу объектов — мы получаем коллекцию с указателями на объекты разных классов. Все они реализуют один и тот же интерфейс, но реализуют его каждый по-своему.
Здравствуйте, LaptevVV, Вы писали:
LVV>При реализации интерпретатора виртуальной машины систему команд удобно представлять иерархией наследования с абстрактным классом во главе.
Слишком тонкий намёк, хотелось бы чуть подробней.
class Mov : public Mnemonic {.....};
class Jmp : public Mnemonic {.....};
Но какие могут быть методы у "Мнемоники вообще"? Там даже execute будет с разным числом параметров.
До паттернов пока рано. Когда начинаются шаблоны я возможно упомяну паттерны и их реализацию метапрограммами Александреску-стайл, но мне кажется это слишком разрушительно. Пока что речь всего лишь о наследовании.
Здравствуйте, vpchelko, Вы писали:
V>В Java очень хорошо наследование показано стандартными коллекциями: List, Map, Set ... etc
Очевидно это сделано из-за убогости джавовских шаблонов, не позволяющих иметь ортогональные алгоритмы и контейнеры. На ранних плюсах писалось много таких контейнерных библиотек для бедных, все умерли. Там дикий оверхед на виртуальные вызовы и марсианские требования к тому что и как туда можно класть.
Как раз на примере vector, map, set, etc я объясняю случаи когда нужно обходиться без наследования.
Здравствуйте, Tilir, Вы писали:
T>Как раз на примере vector, map, set, etc я объясняю случаи когда нужно обходиться без наследования.
Обоснуй
T> ортогональные алгоритмы и контейнеры
По подробнее, я не знаю таких терминов.
T> Там дикий оверхед на виртуальные вызовы и марсианские требования к тому что и как туда можно класть.
Это вы о чем вообще?
П.с. в Java например: есть TreeMap (эквивалент std::map), HashMap, ConcurentTreeMap ConcurentHashMap. У каждой реализации есть свои преимущества. и они реализуют одну суть — ассоциативный массив.
Здравствуйте, koodeer, Вы писали:
K>Про кошечек и собачек может быть и стыдно, но студенты наверняка играют в игры. Вот на примере иерархии игровых объектов стратегии и можно объяснить наследование. Например, базовый класс Unit. От него наследуются всякие там танки, машины, пехота. ...
Про кенгуру, расстреливающее вертолеты, не забыть упомянуть
Некую слабую тень этих возможностей даёт Common Collections API, но это же слёзы. Ковариантные массивы в Java или C# требуют приведения всего к какому-нибудь LCA вида Object или Collection, на чём ортогональность и заканчивается, всё что не Object или Collection идёт лесом.
В любом случае всё это слабоконструктивный оффтоп. Я не ставил целью диспут с джавистами за "у кого лохмаче" -- интернет полон таких диспутов, аргументы сторон, в общем, ясны. Лохмаче у малболга.
Просто: наследование в стандартной библиотеке C++ не комильфо, так что в качестве примера с наследованием vector, list и прочее в лекциях по C++ не подходят совершенно. Возможно в курсе по Java это действительно был бы хороший пример.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Собачек от кошечек наследовать не надо. А вот груши от фруктов или прямоугольники от абстрактной фигуры — это и есть самое простое решение. На этом примере можно показать основные принципы и идеи. А при показе основных принципов нужно использовать как можно более простой пример, дабы не затемнять эти принципы вопросами, не имеющими к ним прямого отношения.
Студент должен иметь возможность сесть и написать программу с этим самым простым примером, чтобы убедиться в его полезности. Эта программа должна решать осмысленную и нетривиальную задачу. Какую программу он может написать про груши и фрукты? Тут ещё рядом предлагали наследовать мяу от пуррр, или наоборот, я не вникал.
Написать граф и, скажем, DFS а потом унаследовать дерево и показать что на нём DFS тоже работает это не особо сложно, всё же не в ПТУ занимаемся. Ну посидеть вечерок, но оно того стоит. Я на первом курсе писал нечто подобное влёт, а у меня сейчас лекции для второго курса и на первом курсе им уже читали C и они уже что-то писали.
Здравствуйте, UA, Вы писали:
UA>На примере какой нибудь GUI библиотеки.
Возможно имеет смысл -- отнаследовать кнопку с прогрессбаром от просто кнопки... должно быть увлекательно и для студентов наглядно. Но тут меня останавливает вопрос какую библиотеку взять за пример няшного ООП? Я в жизни использовал три GUI-библиотеки, ещё когда что-то кодил под винду. Это VCL, MFC и WTL. Все три с точки зрения правильного плюсового ООП -- ад из костылей, тяжелых наркотиков и легаси-кода. К моему великому счастью, последние четыре года я к GUI не прикасался. Я слышал, в QT всё несколько получше, но никогда не смотрел внимательней.
Здравствуйте, Tilir, Вы писали:
T>Здравствуйте, vpchelko, Вы писали:
T>>> ортогональные алгоритмы и контейнеры V>>По подробнее, я не знаю таких терминов.
T>
О чем вообще код?
T>Некую слабую тень этих возможностей даёт Common Collections API, но это же слёзы. Ковариантные массивы в Java или C# требуют приведения всего к какому-нибудь LCA вида Object или Collection, на чём ортогональность и заканчивается, всё что не Object или Collection идёт лесом.
А где такому языку учат? ортогональность? В Java любой объект наследуется от Object.
Ну естествено коллекции должны реализовывать интерфейст Collection — это особенность языка. Тут нет настоящих C++ шаблонов.
T>В любом случае всё это слабоконструктивный оффтоп. Я не ставил целью диспут с джавистами за "у кого лохмаче" -- интернет полон таких диспутов, аргументы сторон, в общем, ясны. Лохмаче у малболга.
Скорее всего шизофазия.
T>Просто: наследование в стандартной библиотеке C++ не комильфо, так что в качестве примера с наследованием vector, list и прочее в лекциях по C++ не подходят совершенно. Возможно в курсе по Java это действительно был бы хороший пример.
Здравствуйте, Tilir, Вы писали:
T>Студент должен иметь возможность сесть и написать программу с этим самым простым примером, чтобы убедиться в его полезности.
Несомненно.
>Эта программа должна решать осмысленную
Разумеется
>и нетривиальную задачу.
А вот с этим решительно не согласен. При изучении некоего нового понятия надо дать как можно более простую и тривиальную задачу, потому что целью является (пока что) изучение этого понятия и его реализации. А вот когда они понятие усвоят и с техникой разберутся — вот тогда можно найти и нетривиальную задачу , пусть делают.
В общем, простое правило обучения — от простого к сложному. А не наоборот.
>Какую программу он может написать про груши и фрукты? Тут ещё рядом предлагали наследовать мяу от пуррр, или наоборот, я не вникал.
Мяу оставим в покое, а , скажем, для набора классов , наследуемых от абстрактной 2D-фигуры, можно сдеать полиморфный список из этих фигур и на его примере показать очень не мало (конструкторы, деструкторы, полиморфизм, время жизни, клонирование и т.д.)
T>Написать граф и, скажем, DFS а потом унаследовать дерево и показать что на нём DFS тоже работает это не особо сложно, всё же не в ПТУ занимаемся. Ну посидеть вечерок, но оно того стоит. Я на первом курсе писал нечто подобное влёт, а у меня сейчас лекции для второго курса и на первом курсе им уже читали C и они уже что-то писали.
T>Пока что мне очень нравится совет от Miroff
в этой ветке. Сесть и написать эмулятор с простыми материальными точками — телами — планетами, студент вполне может.
Пойдет. То же самое, только в 3D. Расчеты траекторий едва ли нужны (пользы от этого немного, не астрономов готовим), а сама иерархия вполне годится. Кстати, одно из упражнений, которое я даю — именно такое, только у меня есть продолжение иерархии : планеты с атмосферой, планеты с жизнью.
Кстати, и Мяу ничем не хуже. Почему иерархия небесных тел годится, а животных нет ?
Здравствуйте, neFormal, Вы писали:
F>тебе нельзя преподавать.
Неизбежный оверхед задавания вопросов на любом русскоязычном форуме: тебе помимо всего прочего рассказывают о том кто ты и зачем нужен. Я всё ждал -- кто же, кто же.
Здравствуйте, Pavel Dvorkin, Вы писали:
>>и нетривиальную задачу.
PD>А вот с этим решительно не согласен. При изучении некоего нового понятия надо дать как можно более простую и тривиальную задачу, потому что целью является (пока что) изучение этого понятия и его реализации. А вот когда они понятие усвоят и с техникой разберутся — вот тогда можно найти и нетривиальную задачу , пусть делают.
Нетривиальная задача в моём понимании это не что-то такое, что сложно запрограммировать, а что-то, что нужно запрограммировать, поскольку это неоправданно долго делается без привлечения ЭВМ и т.п.
PD> скажем, для набора классов , наследуемых от абстрактной 2D-фигуры, можно сдеать полиморфный список из этих фигур и на его примере показать очень не мало (конструкторы, деструкторы, полиморфизм, время жизни, клонирование и т.д.)
Что конкретно делать с этим списком? Пример должен иметь осмысленную постановку и осмысленное применение. "Перемножить матрицы и найти определитель" -- бессмысленная задача. "Подсчитать токи в заданной цепи" -- осмысленная задача.
PD>Кстати, и Мяу ничем не хуже. Почему иерархия небесных тел годится, а животных нет ?
Иерархия небесных тел позволяет прикинуть решение задачи трёх тел, вывести в файл их траектории, посмотреть их с помощью gnuplot. Что делать с иерархией животных?
Здравствуйте, vpchelko, Вы писали:
V>Ну естествено коллекции должны реализовывать интерфейст Collection — это особенность языка. V>Тут нет настоящих C++ шаблонов.
T>Возможно имеет смысл -- отнаследовать кнопку с прогрессбаром от просто кнопки... должно быть увлекательно и для студентов наглядно. Но тут меня останавливает вопрос какую библиотеку взять за пример няшного ООП? Я в жизни использовал три GUI-библиотеки, ещё когда что-то кодил под винду. Это VCL, MFC и WTL. Все три с точки зрения правильного плюсового ООП -- ад из костылей, тяжелых наркотиков и легаси-кода.
Это потому что они соорудили ООП поверх native API которое изначально в процедурном стиле было разработано.
T>К моему великому счастью, последние четыре года я к GUI не прикасался. Я слышал, в QT всё несколько получше, но никогда не смотрел внимательней.
В вашем случае Qt наилучший выбор потому как эмулирует GUI в С++ стиле без особых ограничений со стороны операционных систем.
UA>>На примере какой нибудь GUI библиотеки. AG>Начинающему очень рано в это влезать. AG>Тем более, что "GUI библиотеки" обычно переполнены тонкостями конкретной технической реализации. Что снижает их ценность в данном случае.
"Нативные" переполнены, "не нативные" не переполнены.
Здравствуйте, UA, Вы писали:
UA>>>На примере какой нибудь GUI библиотеки. AG>>Начинающему очень рано в это влезать. AG>>Тем более, что "GUI библиотеки" обычно переполнены тонкостями конкретной технической реализации. Что снижает их ценность в данном случае.
UA>"Нативные" переполнены, "не нативные" не переполнены.
Что в твоем, уважаемый UA, понимании "нативные"/"не-нативные"?
Вот взять, например, тот же MFC — нативная библиотека? А тот же VCL?
А что, например, насчет .NET?
P.S. Если, к примеру, взять объект "кнопка", то у него, по логике ООП, должен быть метод "нажатие" (кликнуть — OnClick), но из-за особенностий ОС, этот метод реализуется (по крайней мере в MFC) в виде события.
В результате, для студентов такой пример, ИМХО, не является лучшей демонстрацией ООП
UA>>"Нативные" переполнены, "не нативные" не переполнены.
AG>Что в твоем, уважаемый UA, понимании "нативные"/"не-нативные"?
"Нативные" используют встроенные возможности операционной системы для построения GUI (например WinAPI) и пользуются как правило встроенной системой сообщений (WndProc).
"Не нативные" эмудируют gui под каждой осью и сами отвечают за обработку gui сообщений.
AG>Вот взять, например, тот же MFC — нативная библиотека? А тот же VCL?
Обе нативные. Так как плотно используют WinAPI для построения окон и обработки сообщений.
QT — не нативная. wxWidgets — нативная.
AG>А что, например, насчет .NET?
WinForms — нативная.
AG>P.S. Если, к примеру, взять объект "кнопка", то у него, по логике ООП, должен быть метод "нажать" (кликнуть), но из-за особенностий ОС, этот метод реализуется (по крайней мере в MFC) в виде события. AG>В результате, для студентов такой пример, ИМХО, не является лучшей демонстрацией ООП
Это обычно проблема нативных gui, хотя никто не мешает сэмулировать нажатие через метод:
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Если конкретно — показать, как работает виртуальная , скажем, print, почему происходит memory leak при невиртуальном деструкторе, и т.д.
Это всё есть. Но это не о наследовании, а чуть дальше. На механике (очень тонкой, включая даже их реализацию на чистом C) работы виртуальных функций, я останавливаюсь очень подробно. Так же и виртуальные деструкторы.
PD>Ради всего святого, объясни, какое отношение задача трех тел имеет к ООП ? Почему учить ООП надо на примере задачи трех тел — не понимаю. Ее на Фортране можно написать, где ООП и в помине не было. И ничем не хуже будет рассчитана траектория.
Потому что мы учим не ООП а C++. C++ язык мультипарадигменный. И если оказывается, что задача трёх тел проще/естественнее считается без ООП, это очень хорошо. Значит студенты должны это дедуцировать и решить её на C++ без ООП. Поэтому я так критичен к примеру наследования. Мне нужно не какое угодно наследование. Мне нужно наследование только такое, чтобы было очевидно, что оно нужно и очевидно зачем оно нужно.
Здравствуйте, 0BD11A0D, Вы писали:
BDA>Если я не прав, откройте свой код и возьмите оттуда столько примеров, сколько надо.
О, это сколько угодно. Сейчас я открою бэкенд LLVM, возьму оттуда сколько надо примеров и студенты повесятся.
Увы, у меня сложновата предметная область. Поэтому я и ищу простые но не дебильные примеры в смежных областях.
BDA>Оффтопик. Главное, что я бы про наследование рассказал — есть места, где оно нужно, поскольку облегчает чтение, а есть места, куда его втыкают либо по глупости, либо из-за ограниченности языка. Пример первого — любая оконная библиотека. Пример второго — boost::operators. Обожаю на диаграмме наблюдать [boost::operators] -> [FileInfo] только потому, что кто-то коллекцию FileInfo решил отсортировать. Не надо так делать. Презренные макросы и то лучше.
Почему оффтопик? На примерах где наследование не нужно и вредно, я останавливаюсь особо.
Здравствуйте, Tilir, Вы писали:
T>Но хотелось бы послушать мнение народа. Как бы вы объясняли наследование?
Не понимаю, чем геометрические фигуры не угодили? Слишком просто что-ли, почему стыдно-то? Есть хорошие примеры реальных таксономий, классификация Линнея, например. Можно попробовать реализовать классификацию, которая таксономией не является, ту же таблицу периодических элементов. Не понимаю, зачем студентам головы дурить сложными примерами, если сама тема и так сложна сама по себе.
Здравствуйте, vpchelko, Вы писали:
V>И какое оно отношение имеет к наследованию?
Не к наследованию, а к вашему примеру наследования на примере List, Set, etc. Необходимость тянуть List от ICollection вызвана именно отсутствием нормальных шаблонов, о чём вам сразу и было сказано.
Здравствуйте, Tilir, Вы писали:
BDA>>Если я не прав, откройте свой код и возьмите оттуда столько примеров, сколько надо.
T>О, это сколько угодно. Сейчас я открою бэкенд LLVM, возьму оттуда сколько надо примеров и студенты повесятся.
T>Увы, у меня сложновата предметная область. Поэтому я и ищу простые но не дебильные примеры в смежных областях.
Ой, да ладно, а. По пути к LLVM вы ни одной задачи не решали? Все эволюционируют от простого к сложному, по дороге прочитав кучу чужого кода и написав кучу своего (не очень удачного).
Мой тезис остается в силе. Я считаю, что хорошо знаю, когда и как нужно наследовать. И никаких примеров ни у кого не прошу. Потому, что все мое понимание строится на этих примерах, а иначе будет как с марксизмом-ленинизмом: подгонка под заданный ответ. А ежели кто просит примеров для объяснения другим, я всегда сильно подозреваю недостаточное собственное понимание.
BDA>>Оффтопик. Главное, что я бы про наследование рассказал — есть места, где оно нужно, поскольку облегчает чтение, а есть места, куда его втыкают либо по глупости, либо из-за ограниченности языка. Пример первого — любая оконная библиотека. Пример второго — boost::operators. Обожаю на диаграмме наблюдать [boost::operators] -> [FileInfo] только потому, что кто-то коллекцию FileInfo решил отсортировать. Не надо так делать. Презренные макросы и то лучше.
T>Почему оффтопик? На примерах где наследование не нужно и вредно, я останавливаюсь особо.
Потому, что не сквозной пример наследования из учебника.
Ну хорошо, по теме.
>Рассказать про открытое наследование интерфейса. Показать как писать чисто абстрактные классы. Рассказать про чисто виртуальные функции.
Наследование в C++ и отношение генерализации — разные вещи. То, о чем идет речь, называется «интерфейс». В одном древнем языке понятие интерфейса с его имманентным ограничением на отсутствие какой бы то ни было реализации отсутствует. Реюзаются классы. Не потому, что С. — кретин, а потому, что эволюция. Интерфейсы в массовых языках появились позже. И рассказывать следовало бы из дня сегодняшнего. И примеры приводить из FCL, где они особенно красивы. А потом показывать, как то же самое делается в старом пердуне, заодно отвечая на вопросы, а почему же в базовой библиотеке так не сделано («Патамушта!»).
>Показать как удобно объекты с одним интерфейсом втыкаются в обобщенный код
Всерьез задумался, где же на обсуждаемом языке это видно. COM/CORBA, разве что, да и то это межъязыковые среды.
>Немного блабла про is-a и LSP
Варварина идея по прошествии многих лет вообще кажется мне... Как бы это сказать... Забавным наблюдением, что ли. Но ни в коем случае не руководством к действию. Английское слово 'principle' одновременно означает как правило/закон, так и неизбежное следствие из закона (природы). Если не брать оригинальную формулировку и рассматривать LS Principle во втором смысле, то получится следующее: если правильно выстраивать иерархии, результат обычно будет удовлетворять LSP. Почему — тема для размышлений в свободное время, как говорят преподаватели. Но если телегу поставить впереди лошади и заставлять проверять правильность иерархий по LSP, потом будем иметь очередной говнокод.
Все блабла можно свести к тому, что есть, дескать, такое наблюдение. Полная эмпирика для нашей инженерной науки. Но в смежной гуманитарной области можно подвести теорию с механизмами.
Что касается правильных иерархий, тут уже написали: классификация рулит.
>Упомянуть про наследование реализации, рассказать про просто виртуальные функции, статический и динамический тип, показать примеры когда это полезно и когда вредно. Особо остановиться на виртуальных деструкторах.
Любая оконная библиотека с невиртуальным Draw() и виртуальными DrawNC()/DrawClient()/... показывает все, что надо.
Что касается деструктора, это обработчик события уничтожения, что на нем останавливаться? Что в нем такого особого?
>Упомянуть про закрытое и защищённое наследование. Объяснить что закрытое это такая композиция (и поэтому лучше делать явную композицию), а защищённое вообще никто не знает зачем нужно.
Язык слишком... либеральный. То в нем в интерфейсах реализация допускается, то неоткрытое наследование. Кто-то потом на подобной уродливости строит грязные хаки («потому, что могут», ага), а их дурацкий комитет потом не дает запретить. Так это и надо рассказывать.
>Рассказать про RTTI и dynamic_cast.
Недометаданные в языке без базового класса Object выродились в уродливый костыль. Наследование тут не при чем. Нужда в нем обычно возникает, когда в реальной программе под Уиндоус приходится гонять через WinAPI объекты.
Это не академическое ковыряние в носу, извините. Это жизнь и зарабатывание денег. Чему я бы и учил в первую очередь.
Здравствуйте, 0BD11A0D, Вы писали:
BDA> А ежели кто просит примеров для объяснения другим, я всегда сильно подозреваю недостаточное собственное понимание.
ЧСВ over 9000?
Ни у кого, никогда не может быть достаточного понимания языка C++. Даже его создатель знает его на 6/10. Впрочем, учитывая дальнейшее...
Я, пожалуй, соберу ваши основные мысли в кучку, слитное чтение всего этого бугурта немного поднимает мне настроение:
BDA> В одном древнем языке понятие интерфейса с его имманентным ограничением на отсутствие какой бы то ни было реализации отсутствует. BDA> Язык слишком... либеральный. То в нем в интерфейсах реализация допускается, то неоткрытое наследование. Кто-то потом на подобной уродливости строит грязные хаки («потому, что могут», ага), а их дурацкий комитет потом не дает запретить. Так это и надо рассказывать. BDA> Недометаданные в языке без базового класса Object выродились в уродливый костыль. Наследование тут не при чем. Нужда в нем обычно возникает, когда в реальной программе под Уиндоус приходится гонять через WinAPI объекты. BDA> Это не академическое ковыряние в носу, извините. Это жизнь и зарабатывание денег. Чему я бы и учил в первую очередь.
В общем, с вами всё ясно. Вступать в диспуты с дотнетчиками здесь я считаю таким же оффтопом как и с джавистами. Интернет также полон ими. Ваш тон offensive enough, но и я со своей стороны презираю скриптоязыки для виртуальных машин и в общем согласен на взаимность. Посоветовать что-либо вы мне вряд ли сможете.
Но не удержусь прокомментировать главный перл:
BDA>Что касается деструктора, это обработчик события уничтожения, что на нем останавливаться? Что в нем такого особого?
Понимаете, виртуальный деструктор это то, что надо знать в первую очередь и как отче наш. Зачем он нужен, что будет если его не объявить и отнаследоваться и т.д. Но не в вашем случае, когда вы скриптуете виртуальную машинку и за вами всё чистит сборщик мусора, а в случае, если мы пишем на языке, где память это ресурс и им надо управлять.
Здравствуйте, Tilir, Вы писали:
T>Допустим вы построили иерархию геометрических фигур. Или классификацию Линнея. Что дальше вы будете делать с этой иерархией классов? Какие конкретно задачи вам поможет решить наследование, какие без него были бы неоправданно сложны? T>Если ответ "никакие", значит это контрпример -- пример ситуации, когда наследование избыточно и поэтому не нужно.
Что значит "без него"? Построение программной модели в виде иерархии классов -- это основной механизм решения задачи в языке C++. Да, конечно, поддерживается и функциональная парадигма и обобщенное программирование. Но, вроде бы в данном случае изучается ООП, так что его и надо проиллюстрировать. Преимущества и недостатки той или иной парадигмы это же отдельная тема. Я вообще сомневаюсь в том, что эту тему студентам надо доносить.
Здравствуйте, Tilir, Вы писали:
BDA>> А ежели кто просит примеров для объяснения другим, я всегда сильно подозреваю недостаточное собственное понимание.
T>ЧСВ over 9000?
Нет, жизненный опыт. Один дурачок мне глаза открыл дюжину лет назад. Он мне доказывал что-то типа распространения бесплатного образования в Германии, весьма оригинальным способом: зашел на свой любимый трепный форум и попросил накидать примеров. Ему накидали ссылок на немецком, он их притащил мне — на, типа, переводи и читай сам. Доказано Занусси. Я офонарел от этого и впервые задумался, что у других мышление может отличаться. Для меня просить примеры для доказательства или объяснения — дико. Это показатель (не)владения темой, то есть, повод задуматься, откуда я взял те мысли, которые собираюсь доказывать или объяснять. С тех пор я этот паттерн то и дело встречаю в жизни. Но пока не доводилось у преподавателей.
T>Ни у кого, никогда не может быть достаточного понимания языка C++. Даже его создатель знает его на 6/10. Впрочем, учитывая дальнейшее...
Тема называется «Объясняем наследование». C++ я знаю балла на 3 из 10 и не стесняюсь в этом признаться. За точку отсчета в 10 баллов я беру Пашу Кузнецова, который стандарт знает назубок и одного чувака, который меряет for fun производительность метапрограммирования разных компиляторов. А вы не читаете, что вам пишут. Красной, как говорится, нитью, проходило у меня: не путайте наследование в C++ и наследование вообще. C++ полон чудес и legacy-ограничений.
Теперь я проверил то, что и подозревал: учить наследованию просто вы не собирались. Воспитываете еще одно поколение упертых и зашоренных плюсовиков.
T>Я, пожалуй, соберу ваши основные мысли в кучку, слитное чтение всего этого бугурта немного поднимает мне настроение:
Не все то бугурт, что воняет. Бывает просто банан критика.
BDA>> В одном древнем языке понятие интерфейса с его имманентным ограничением на отсутствие какой бы то ни было реализации отсутствует. BDA>> Язык слишком... либеральный. То в нем в интерфейсах реализация допускается, то неоткрытое наследование. Кто-то потом на подобной уродливости строит грязные хаки («потому, что могут», ага), а их дурацкий комитет потом не дает запретить. Так это и надо рассказывать. BDA>> Недометаданные в языке без базового класса Object выродились в уродливый костыль. Наследование тут не при чем. Нужда в нем обычно возникает, когда в реальной программе под Уиндоус приходится гонять через WinAPI объекты. BDA>> Это не академическое ковыряние в носу, извините. Это жизнь и зарабатывание денег. Чему я бы и учил в первую очередь.
T>В общем, с вами всё ясно. Вступать в диспуты с дотнетчиками здесь я считаю таким же оффтопом как и с джавистами. Интернет также полон ими. Ваш тон offensive enough, но и я со своей стороны презираю скриптоязыки для виртуальных машин и в общем согласен на взаимность. Посоветовать что-либо вы мне вряд ли сможете.
Я не дотнетчик. А шарп с явой не скриптоязыки, JFYI. Но вы мне открылись с новой стороны. Вот с этой: http://rsdn.ru/forum/job/5746297.1
T>Но не удержусь прокомментировать главный перл:
BDA>>Что касается деструктора, это обработчик события уничтожения, что на нем останавливаться? Что в нем такого особого?
T>Понимаете, виртуальный деструктор это то, что надо знать в первую очередь и как отче наш. Зачем он нужен, что будет если его не объявить и отнаследоваться и т.д.
Нет, не понимаю. Сделайте то же самое с обработчиком любого другого события — получите глюк. И этот (утечка ресурсов) даже не самый страшный. Самый страшный — порча памяти, затрагивающая vtbl. Вот где жопа, но на нее хитрый болт пока не придумали. GC, разве что.
>Но не в вашем случае, когда вы скриптуете виртуальную машинку и за вами всё чистит сборщик мусора, а в случае, если мы пишем на языке, где память это ресурс и им надо управлять.
Дотнетовский рантайм не виртуальная машинка. Офенсив офенсиву рознь. Потому, что вы уже начали искажать факты.
UPDATE: а впрочем, черт его знает. Все это майкрософтовский маркетинг, по большому счету. Продемонстрировать преимущество перед Джавой прямо с порога. Забираю назад предыдущий абзац.
Здравствуйте, Tilir, Вы писали:
T>Не к наследованию, а к вашему примеру наследования на примере List, Set, etc. Необходимость тянуть List от ICollection вызвана именно отсутствием нормальных шаблонов, о чём вам сразу и было сказано.
Я этого не заметил.
А что плохого, что они наследуются от Collection и Iterable?
Здравствуйте, Dufrenite, Вы писали:
... D>Самое простое, это объяснить на примере компьютерной игры.
D>Привожу пример из жизни:
D>1. D>class Actor;
D>2. D>class Character : public Actor; D>class Vehicle : public Actor; D>class Trigger : public Actor; D>class Door : public Actor;
D>3. D>class Player : public Character; D>class Enemy : public Character;
D>class Tank : public Vehicle; D>class Helicopter : public Vehicle;
D>...
Да, действительно просто. Только поясни — как с помощью этой иерархии найти все юниты, которые летают?
Здравствуйте, Dufrenite, Вы писали:
... 1>>Да, действительно просто. Только поясни — как с помощью этой иерархии найти все юниты, которые летают?
D>1. IVisitor D>2. virtual bool IsFlying() D>... D>1000. dynamic_cast<IFlyingUnit>()
Так ведь иерархия не используется. С таким же успехом можно обойтись вообще без наследования.
D>Дайте контекст — обсудим.
А я вот именно не это и намекаю — без контекста и понимания логики построение иерархии — мартышкин труд. В лучшем случае он пропадёт, а в худшем — приведёт к проблемам в будущем.
Здравствуйте, 1303, Вы писали:
1>Так ведь иерархия не используется. С таким же успехом можно обойтись вообще без наследования.
Я понял в чём дело. У меня так называемый data-oriented подход. Главное правильно разместить данные, а алгоритмы обхода делаются по мере надобности. Все другие способы построения иерархий классов, в частности с привязкой к реальному миру приводят к эпик-фейлам.
1>А я вот именно не это и намекаю — без контекста и понимания логики построение иерархии — мартышкин труд. В лучшем случае он пропадёт, а в худшем — приведёт к проблемам в будущем.
Я вам привёл пример как это делается в реальный проектах. В частности, эта иерархия мной взята (по памяти, возможно не совсем точно) из Unreal Engine 4.
Здравствуйте, Dufrenite, Вы писали:
... 1>>Так ведь иерархия не используется. С таким же успехом можно обойтись вообще без наследования.
D>Я понял в чём дело. У меня так называемый data-oriented подход. Главное правильно разместить данные, а алгоритмы обхода делаются по мере надобности.
Не понял — что значит правильно разместить данные.
1>>А я вот именно не это и намекаю — без контекста и понимания логики построение иерархии — мартышкин труд. В лучшем случае он пропадёт, а в худшем — приведёт к проблемам в будущем.
D>Я вам привёл пример как это делается в реальный проектах. В частности, эта иерархия мной взята (по памяти, возможно не совсем точно) из Unreal Engine 4.
Ну вот а в другом реальном проекте будет совершенно иное. Но предлагаю тут с этим закругляться, тема про проблемы ООП где-то рядом была.
Здравствуйте, Куть, Вы писали: К>Эту иерархию классов можно использовать в написании простенького графического редактора.
Начинается...
Нельзя! К>На примере фигур можно показать, что код с наследованием получается проще и понятнее: К>1. Можно нарисовать все фигуры, пробежав по списку указателей std::list<Figure *> и вызывая виртуальный метод draw(). К>2. Можно найти сумму площадей в пару строчек. К>3. Можно сохранить данные всех фигур в файл, заодно можно рассмотреть проблему с чтением из файла и её решение в виде фабричного метода.
К>А перед этим можно показать пример, где часть этих задач решается без наследования, с помощью длинных switch(type) или указателей на функции.
А после этого можно показать пример, где все эти задачи решаются без наслежования и без switch, и без указателей на функции.
Вменяемый инженер всё сделает через примитив Path, состоящий из прямых, арок, и кривых Безье, а все остальные "фигуры" будут просто конструкторами Path с различными аргументами.
Потому, что все ваши "простые и понятные" примеры сдохнут сразу же, как я попрошу вас реализовать мне операцию Intersect для двух фигур.
К>И добить добавлением нового типа фигуры. С наследованием будет добавлен один класс (изменения локализированы), а без наследования изменения будут размазаны по всей программе.
А без наследования все изменения сведутся к написанию ещё одной функции.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Вы всё правильно говорите, и про intersect() я сам думал — писать или нет. Видимо надо было написать.
Ключевое слово — "простенький" редактор. Такой, в котором не нужно искать пересечения и использовать математику сложнее школьной для расчета площадей фигур.
Плюс графического редактора — наследование можно объяснить в терминах, понятных последним двоечникам. Тогда как понятия "кривая Безье" или "агрегатор" могут вообще ничего не говорить половине студентов.
Ваше предложение ниже мне однозначно нравится больше, но вариант с редактором тоже имеет право на существование.
Здравствуйте, Куть, Вы писали:
К>Вы всё правильно говорите, и про intersect() я сам думал — писать или нет. Видимо надо было написать. К>Ключевое слово — "простенький" редактор. Такой, в котором не нужно искать пересечения и использовать математику сложнее школьной для расчета площадей фигур. К>Плюс графического редактора — наследование можно объяснить в терминах, понятных последним двоечникам. Тогда как понятия "кривая Безье" или "агрегатор" могут вообще ничего не говорить половине студентов.
Опасность этого эксперимента — в том, что многие и до седин упорно считают, что именно так и надо писать графические редакторы.
Получается какой-то парадокс: мы берём концепцию, которая должна помогать нам решать сложные задачи, и внезапно оказывается, что всё наоборот — для синтетической упрощённой задачи она ещё хоть как-то подходит, а стоит задачу чуть усложнить — всё, приплызд, отматывай назад. Вся диаграмма классов летит в корзину, объём кода сокращается в шесть раз, функциональность вырастает в два.
Я, будучи на месте вот такого "студента", почувствовал себя, мягко говоря, обманутым в момент осознания.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Dufrenite, Вы писали:
... 1>>Не понял — что значит правильно разместить данные.
D>Термин "декомпозиция" вам знаком?
В общих чертах. Правда, я не уверен, что это как-то связано с наследованием.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Dufrenite, Вы писали:
D>>Я понял в чём дело. У меня так называемый data-oriented подход. Главное правильно разместить данные, а алгоритмы обхода делаются по мере надобности. Все другие способы построения иерархий классов, в частности с привязкой к реальному миру приводят к эпик-фейлам.
S>Вот тут опять есть нюансы. На моей памяти, попытка выстроить иерархию наследования на основе данных как раз и приводит к эпик фейлам. S>Классический пример — квадрат/прямоугольник/эллипс/круг. Вроде бы и про данные всё понятно, а иерархия никак не вытанцовывается.
Согласен, если рассматривать иерархию как "вещь в себе". А если рассмотреть её в контексте архитектурного паттерна, например MVC, то это имеет смысл.
В этом случае я бы спроектировал следующие взаимодействующие иерархии:
1. Model:
class Shape;
class Square : public Shape;
class Rectangle : public Shape;
...
1.1 Visitor:
struct IShapeVisitor;
2. View:
class ShapeDrawer : private IShapeVisitor;
class ShapeNetworkSender : private IShapeVisitor;
...
3. Controller:
class CanvasController;
...
S>Единственный понятный мне способ ОО-проектирования — это плясать от поведения. S>Ну, то есть решаем задачу в стиле "ок, вот у нас есть идеальный "решатель задачи" для каждой сформулированной нами задачи. Давайте посмотрим, можно ли выстроить какую-то классификацию этих решателей; можно ли выделить из их ответственностей какие-то фрагменты, которые сделают общее решение проще и понятнее".
К сожалению, я сталкивался с ситуациями, когда попытки построить иерархию на основе поведения приводили к разрастанию иерархии до безумных размеров с совершенно головоломной логикой наследования.
Если же отделить "мух от котлет", то есть данные от поведения, то можно получить минималистичные и чётко сфокусированные иерархии, не имеющие абсолютно ничего лишнего.
S>Важный момент — речь идёт про "решатель задачи", а не просто про "класс для представления сущности из предметной области". S>Например, если у нас речь шла про геометрические фигуры, то "задача" — это типа "нарисовать на экране прямоугольник", и решатель, соотвественно — это "рисователь прямоугольников на экране", а вовсе не "класс Прямоугольник с методом Draw". S>В результате такой декомпозиции мы быстро получим "рисователя отрезков на экране" и "рисователя ломаных на экране", а рисователи прямоугольников, треугольников, квадратов и прочих фигур исчезнут за ненадобностью.
Можно и к этому придти, но я предпочитаю базировать слой более специфичных классов на слое более общих.
В этом случае дальнейшее развитие нашего модельного примера могло бы выглядеть как-то так:
2. View:
class ShapeDrawer : private IShapeVisitor;
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Dufrenite, Вы писали:
S>Единственный понятный мне способ ОО-проектирования — это плясать от поведения.
И в результате данные и поведение перемешиваются в винегрет, т.к. как типы данных сильно реже меняются, нежели чем алгоритмы их обработки, теперь я понимаю почему вы мне поставили минус Учите мат. часть и ваши программы станут шелковистее.
Классическая иерархия Figure.Draw нарушает SRP. Когда мы делали рисовалку на DirectX, то базовый объект у нас выдавал массив объектов которые надо отрисовать, рисовальщик был отдельно, т.к. алгоритм отрисовки и её необходимость не зависит от того когда объект рисуется.
Здравствуйте, diez_p, Вы писали: S>>Единственный понятный мне способ ОО-проектирования — это плясать от поведения. _>И в результате данные и поведение перемешиваются в винегрет, т.к. как типы данных сильно реже меняются, нежели чем алгоритмы их обработки,
Не вижу, как вы сделали переход от посылки к результату.
_>Классическая иерархия Figure.Draw нарушает SRP. Когда мы делали рисовалку на DirectX, то базовый объект у нас выдавал массив объектов которые надо отрисовать, рисовальщик был отдельно, т.к. алгоритм отрисовки и её необходимость не зависит от того когда объект рисуется.
Совершенно верно. Вы пришли к правильному решению задачи. Внезапно оказалось, что вместо развесистой иерархии фигур вы получили фиксированный набор примитивов.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Dufrenite, Вы писали: D>Согласен, если рассматривать иерархию как "вещь в себе". А если рассмотреть её в контексте архитектурного паттерна, например MVC, то это имеет смысл.
Вот это меня опять настораживает. Я имею в виду — ввод паттернов на ровном месте.
D>В этом случае я бы спроектировал следующие взаимодействующие иерархии:
D>1. Model: D>class Shape; D>class Square : public Shape; D>class Rectangle : public Shape; D>...
D>1.1 Visitor: D>struct IShapeVisitor;
D>2. View: D>class ShapeDrawer : private IShapeVisitor; D>class ShapeNetworkSender : private IShapeVisitor; D>...
D>3. Controller: D>class CanvasController; D>...
Ух ты ж блин! Сколько всего. И всё это вместо пары классов Path и Canvas.
Круто, чё.
D>К сожалению, я сталкивался с ситуациями, когда попытки построить иерархию на основе поведения приводили к разрастанию иерархии до безумных размеров с совершенно головоломной логикой наследования.
Вынужден поверить вам на слово, хотя сам такого не видел.
D>Если же отделить "мух от котлет", то есть данные от поведения, то можно получить минималистичные и чётко сфокусированные иерархии, не имеющие абсолютно ничего лишнего.
Тут — согласен. Как раз чрезмерное увлечение слиянием данных с поведением и является обычной причиной чудовищных ОО-решений.
D>В этом случае дальнейшее развитие нашего модельного примера могло бы выглядеть как-то так:
D>2. View: D>class ShapeDrawer : private IShapeVisitor;
D>4. Render: D>class LineRenderer D>{ D>public: D> void DrawLines(const std::vector<Point>& points, const Color& color); D>};
А какой код и сколько нам потребуется в ShapeDrawer? Я же правильно понял, что в нём будет по методу на каждого потомка Shape?
Простите, но такой код хорош только для списания затрат по time spent basis.
Для реальной работы надо минимизировать усилия. В вашем коде стоимость добавления новой фигуры — запредельна.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Вот это меня опять настораживает. Я имею в виду — ввод паттернов на ровном месте.
Даже не знаю что ответить. Чем вас паттерны настораживают?
S>Ух ты ж блин! Сколько всего. И всё это вместо пары классов Path и Canvas. S>Круто, чё.
Вы всю логику хотите в 2 класса поместить? Видал я классы по 5000 строк, но уверяю что это неверный путь.
D>>К сожалению, я сталкивался с ситуациями, когда попытки построить иерархию на основе поведения приводили к разрастанию иерархии до безумных размеров с совершенно головоломной логикой наследования. S>Вынужден поверить вам на слово, хотя сам такого не видел.
Логически подумайте. Чем больше разнообразных задач вы навесите на иерархию, тем больше в ней будет нестыковок, костылей и компромиссов.
Идеально разнести задачи по разным классам или иерархиям. В этом случае понимание кода сильно упрощается.
S>А какой код и сколько нам потребуется в ShapeDrawer? Я же правильно понял, что в нём будет по методу на каждого потомка Shape?
Верно. Всё, что связано с рисованием будет в нём.
S>Простите, но такой код хорош только для списания затрат по time spent basis. S>Для реальной работы надо минимизировать усилия. В вашем коде стоимость добавления новой фигуры — запредельна.
Добавление одного метода из 10 строк для вас запредельная сложность?
Здравствуйте, Sinclair, Вы писали:
S>Тем же самым — паттерн должен вводиться не как самоцель, а как решение какой-то задачи. Причём так, что "без паттерна" решение получается хуже по объективным характеристикам.
В данном случае паттерны сильно упрощают жизнь.
S>Не вижу никакой логики, равно как и причин, по которым вы предлагаете мне навесить на единую иерархию множество разнообразных задач. S>Проектирование от поведения совершенно не об этом.
Ну вы же не думаете, что развитие вашей программы ограничится простым отображением фигур на экране?
S>С чего это "одного"? В вашем решении мне нужно: S>1. Добавить новый класс фигуры. Сколько в нём будет строк кода? S>2. Добавить новый метод в IShapeVisitor S>3. Добавить новый метод в каждую реализацию IShapeVisitor. В вашем примере их две.
Вам в любом случае придётся как-то эту функциональность реализовать.
S>В моём решении единственное, что потребуется добавить во весь проект — это как раз один метод. S>Примерно вот так: S>
S>public static Path CreateRightAngleTriangle(Pen pen, Point corner, int height, int width)
S>{
S> return Path.CreateClosedPath(pen, corner, corner + new Offset(0, -height), corner + new Offset(width, 0));
S>}
S>
S>Всё. А теперь приведите мне тот код, который потребуется написать в вашем проекте, чтобы заработали прямоугольные треугольники. S>Если я попрошу вас научить вашу программу поворачивать Shape на произвольный угол, то вы мгновенно утонете в коде. А у меня добавится ровно один метод Path RotatePath(Path original, double angle). Никакого полиморфизма, никакого размазывания изменений по всем файлам проекта, минимум риска сделать ошибку.
Вы ошибаетесь. Я добавлю в класс Shape матрицу трансформации. Соответственно в методе DrawLines появляется ещё один параметр.
А теперь я вас попрошу внести в ваше решение маленькое изменение: пусть квадрат заливается текстурой, которую я назначил. А ещё я хочу, чтобы вокруг треугольника отображалось гало. А ещё хочу экспорт 3D моделей из 3D Studio Max. И ещё чтобы можно было крутить в трёх измерениях. И экспортировать в популярные форматы, включая PDF.
Здравствуйте, Dufrenite, Вы писали:
D>Вам в любом случае придётся как-то эту функциональность реализовать.
Функциональность — да. А вот весь код ради кода (т.е. визиторы, override, и прочий мусор) — нет, не придётся.
D>Вы ошибаетесь. Я добавлю в класс Shape матрицу трансформации. Соответственно в методе DrawLines появляется ещё один параметр.
Матрицу-то вы добавите. А как вы предполагаете её использовать и где? Пример кода — в студию.
Мой — вот он:
public static Path RotatePath(Path original, double angle)
{
var m = TransformMatrix.CreateRotationMatrix(angle);
var rotatatedSegments = from s in original.Segments select m.Transform(s);
return new Path(rotatedSegments);
}
подразумеваем, естественно, что мы написали хелпер-метод
public static PathSegment Transform(this TransformMatrix m, PathSegment segment)
{
var s = segment.Clone();
s.Point1 = m.Transform(s.Point1);
s.Point2 = m.Transform(s.Point2);
return s;
}
D>А теперь я вас попрошу внести в ваше решение маленькое изменение: пусть квадрат заливается текстурой, которую я назначил.
Без проблем. Мы просто добавим в наш единственный метод DrawPath(Path path) опциональный параметр Brush fill.
И забесплатно получим возможность заливать текстурами не только квадраты, а и треугольники, и прямоугольники, и все будущие фигуры.
D>А ещё я хочу, чтобы вокруг треугольника отображалось гало.
Подход — такой же. Придумываем параметры гало (скорее всего цвет, насыщенность, и поперечник), и добавляем метод, который их использует.
D>А ещё хочу экспорт 3D моделей из 3D Studio Max. И ещё чтобы можно было крутить в трёх измерениях.
Ну, для начала придётся перейти от Path к 3dSurface. Затем принять решение, что из данных 3DS нам будет интересно — там, вообще-то, дохрена всего. С учётом того, что это — параметрический 3d-аниматор, а не просто "рисовалка 3d моделей".
А уже затем мы напишем совершенно отдельный от всего код лоадера 3DS, который будет читать файлы и отдавать те объекты, которые нам интересны.
D>И экспортировать в популярные форматы, включая PDF.
Ну, экспорт в PDF нам будет доступен забесплатно, потому что есть готовые экспортилки, которые реализуют тот же самый Canvas (он же DeviceContext), который мы использовали в нашем методе DrawPath.
D>Дальше продолжать?
Да сколько угодно. Я только по-прежнему не понимаю, как тут поможет ваша иерархия Shape и иерархия Visitor.
Во всех этих задачах она будет только путаться под ногами. И будет существенно усложнять процесс добавления нового Shape в проект.
Если вы собираетесь мне возразить, то будьте любезны привести пример кода.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.