воскресенье, 28 июля 2013 г.

ПМО ИУС — ООП — Абстрагирование и типизация

Изначально мне и нам преподносили ООП с теоретическом стиле Буча — программные системы становятся все сложнее, понятие сложности, с этим надо что-то делать, … К этому всему эволюция программных парадигм (формулы, алгоритмы, подпрограммы, модули, …). Как показывала практика, понимание и усвоение с такой подачи шло очень слабо. По моему мнению, это происходило из-за ряда причин: приличный кусок теории, не связанной с практикой (просто большой объем такой информации воспринять и переварить сложно); отсутствие у студентов опыта написания и понимания сложных систем; новизна большого числа понятий, которые появлялись на практически пустом месте.

Такой подход по моему мнению, конечно классический, но через данный барьер проходили не многие (мало кто в конце 2003-2004 года мог толком сказать чем класс отличается от объекта), а ещё хорошо помню с какими потерями в свое время мне пришлось изучать ООП самому. Хотелось чтобы это не оставалась как система выживания (типа кто хочет, тот научится), а минимизировать порог вхождения для понимания основных сущностей в ООП.

Для решения данных проблем было предпринято ряд действий. Во-первых, все ООП было аккуратно распилено на 6 разных частей (абстрагирование, типизация, модульность, наследование, иерархия, полиморфизм). Каждая часть — это отдельный пункт, с которым надо отдельно разобраться, а разбор проходил в виде отдельной лекционной части, не залезая в другие. Аналогичное деление происходило в лабораторных — когда в каждой из них акцент ставился на определенные моменты. Во-вторых, вся история появления ООП была выброшена из потенциальных конспектов и преподнесение делалось как есть (как создать класс, что такое объект, как можно эти оперировать, какие преимущества и недостатки). В-третьих, психологически практически полностью выброшен пугающий подтекст (сложность, большие системы, …), т.е. да, ООП может показаться и оказаться сложным, но ничего сложного в нем нет. В-четвертых, в материале были выброшены все малые детали, а акцент был только на больших сущностях, которые прокручивались по нескольку раз на разных лекциях (важно первое приближение, а детали уже в случае частных вопросов). В-пятых, материал не только как есть, но вокруг него ряд вопросов-ответов (почему так; зачем; что это дает; как это сделать; вот аналогичный в жизни пример, но не на языке С++; когда применять а когда воздержаться;…).

В результате такого применения ядро предоставляемого материала лучше усваивалось в массе, частности уже особо прогрессивные студенты спрашивали и осваивали сами, а детали обсуждались в случае частных проблем. Другими словами, мастерство — это до блеска отточенная база. Акцент ставился на базу, она должна быть освоена и отточена, остальное по желаниям и возможностям.

Абстрагирование и типизация

Абстрагирование не принадлежит монопольно ООП, а может быть рассмотрено отдельно. Кроме того, это первый шаг в разработке, а улучшение его понимания влияет на многое.

Мы с раннего детства умеем абстрагировать, но не знаем, что это так называется (; Если у нас есть корзинка фруктов и мы хотим узнать сколько там яблок, то для этого нам нужно провести абстрагирование. У нас есть задача (посчитать количество яблок), мы представляем что такое яблоко (можем его отличить от другого предмета в корзинке), умеем считать (ответ — число). Мы выбираем только те сущности и свойства реального мира, которые важны в нашей задаче. Нам важны только яблоки, и только их число. Нас не интересуют груши и грибы, нам не интересны спелые ли яблоки или червивые, нам важны только яблоки и только их число. Выделение нужных свойств из всего разнообразия — есть готовое абстрагирование.

Первая из лабораторных ориентирована в самом начале (после первых шагов в SVN) на абстрагирование. Раньше такой ориентации не было. Если посмотреть на примеры выполнения первых ООП-работ 2003-2005 гг. (которые пришли мне в наследство), то можно заметить, что процесса ООП-абстрагирования нет вообще, а в мыслях об ООП рождается впечатление, что это такой хитрый способ сборки функций в одном месте и рядом лежащих данных, а с таким высказываемым студентами впечатлением на л.р. я сталкивался постоянно. Когда сейчас смотрю на эти примеры, то понимаю, что это выглядит как будто ООП (классы, объекты), но это не ООП, а процедурное программирование, обернутое в классы и объекты.

Для переориентации было сделано два принципиальных шага. Первый — создание знакомой предметной области. Второй — необходимость студенту перевести эту предметную область в абстрактные действия (выполнить абстрагирование), которые могли даже противоречить с реальностью. Ниже три примера заданий на первую л.р.:

  1. Кот Васька регулярно охотится на воробьёв в своём дворе. При чём после удачной охоты запирает воробьёв в клетку до конца сезона.

    Во двор каждый день прилетают новые воробьи, и кот Васька знает, сколько каждый день прилетит новых. Старые же воробьи из двора не улетают.

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

  2. Дюймовочка плывёт на кувшинке и спасается от жабы. Согласно первоисточнику, течение было очень сильным (M м/с) и поэтому жаба отставала и не смогла догнать. Согласно законам физики, течение действует одинаково как на Дюймовочку, так и на жабу. Но, о чём умалчивает первоисточник, жабу слишком жёстко заносило на поворотах. Поэтому Дюймовочка двигалась с постоянной скоростью M м/с, а жаба ускорялась с ускорением X м/с^2 и на поворотах её скорость падала до нуля. Подлинной истории неизвестно, догнала жаба Дюймовочку или нет, но известны длины участков реки, а также то, что жаба стартовала со скоростью 0 м/с.

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

  3. Когда варкается, хливкие шорьки превращаются в мюмзиков. При чём за один такой день - одно превращение. Если не варкается, то количество мюмзиков увеличивается на 2. В зависимости от погоды каждый день варкается или нет.

    Спроектировать класс превращений, для которого определить методы прихода некого числа мюмзиков и метод прихода некого числа шорьков, а также метод прохождения дня когда варкается, и метод прохождения дня когда не варкается.

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

Первые шаги в работе — проведение абстрагирования. Необходимо исходя из задания выделить необходимые для решения сущности (данные и код, поля и методы). Этот процесс происходит не у всех и не сразу, что тоже вполне естественно,и также естественно то, что большинству студентов требуется помощь — что и имеет место, когда мы начинаем лично разбирать задачу — что происходит, какие объекты, в каких они могут быть состояниях, что для нас важно а что не важно и т.д. (это вполне естественно потому, что способность к проведению абстрагирования с точки зрения ООП приобретается только с опытом, а у студентов пока что опыта нет). Таким образом в результате абстрагирования собирается класс, позволяющий решить задачу. Из одного и того же задания класс не всегда получается одинаковый (может быть даже пара классов, типы полей студент выбирает сам исходя из своих соображений, …).

Следующие шаги — каркас уже готов (поля и интерфейсы методов), собирается функциональность и проверяется её работа. В этой работе нет никакой инкапсуляции (все в public), обязательна демонстрация доступа к полям и методам класса напрямую (чтение, запись, вызов). Кроме того, необходима сборка конструктора по умолчанию и конструктора с параметрами, задающего полное начальное состояние объекта, и, соответственно, проверка их работы. Все в одном cpp-файле, ничего лишнего (так как ещё тут есть SVN, консольная среда Builder считается новой).

Таким образом, акцент идет на абстрагировании и типизации (создание своего типа, первые шаги; другие элементы типизации позже), а фокус держится прежде всего на базовыя понятиях класса/объекта, объединения кода и данных, а также их применения.

Подробности

ООП можно разбить на 6 частей, каждая из которых достаточно независима от другой, и их мы будем стараться рассматривать отдельно. Совсем отдельно рассмотреть не получится, так как они взаимосвязаны, поэтому мы берем ласты и шапочку и ныряем в ООП, надеясь разобраться.

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

С точки зрения отношения объектов друг относительно друга они чаще всего не являются независимыми, а используются как иерархии абстракций (пара примеров когда общая абстракиция выносится наверх а детали идут вниз).

Объект — это код (действия) плюс данные (поля). Если мы возьмем абстрагированную лампочку, то она будет иметь какое-то нам важное состояние (поля) и над ней можно будет делать какие-то нам важные действия (код).

Каждый объект имеет состояние (исходя из полей), поведение (исходя из методов) и идентичность (некоторые средства, позволяющие отделить один объект от другого).

Создавать каждый раз новый объект с нуля неудобно. Чаще всего получается так, что мы имеем много лампочек с похожим поведением, но при этом каждая из них имеет свое состояние. В связи с этим удобно сделать некоторый шаблон, по которому штамповать лампочки. Так вот, таким шаблоном является класс, а объект — это уже конкретная лампочка.

Близкой аналогией является тип и переменная. Тип он один, и согласно типу вы можете создавать переменные. Каждая переменная уникальная и имеет свое личное состояние. При этом она всегда принадлежит какому-то типу. Таким образом, тип это такой шаблон, с помощью которого можно создавать переменные. Т.е. тип — это класс, а переменная — объект.

Синтаксис в С++ для типов/переменных и классов/объектов аналогичен. Также можно создавать указатели определенного класса на объекты, массивы объектов определенного класса, присваивать один другому и др..

Само определение ООП звучит так (база объекты, объект является экземпляром какого-то класса, есть иерархия).

Этапы ООП — абстрагирование (изучили), проектирование (сборка классов-объектов и взаимоотношений между ними) и программирование.

Пример иерархии классов и каков в ней смысл (общее объединяется и выносится наверх; это общее может быть как код, так и данные).

Примеры того, как это может быть в С++ (лучше в Real-Time с нуля) — просто пустой класс, добавление 1-2 полей, запись-чтение, добавление функций (методов), вызов функции, работа в контексте класса, две формы записи тела метода (внутри класса и вне), функции-помощники класса (объекты можно передавать как параметры).

Проблема инициализации объектов — можно забыть или сделать два раза, нужна отдельная функция, отдельная функция может быть разная в зависимости от программиста. Решение — выделение специальной функции под специальным синтаксисом — и это функция конструктор. Бывают с параметрами и без, может быть несколько (для разных вариантов инициализации). Позволяют контролировать инициализацию — чтобы никто не передал некорректных параметров, а если, то отреагировать на это и оставить объект в корректном состоянии.

Специальная функция перед уничтожением объекта — деструктор.