Класс CPolygon
Каждый объект класса CPolygon должен иметь связь с данными документа. Это осуществляется путем запоминания адреса документа в переменной m_pDoc, которая играет роль обратного указателя. Такой прием, когда вложенный объект помнит адрес объемлющей его структуры данных, очень распространен в объектно-ориентированном программировании. Он существенно упрощает обмен данными между двумя объектами.
Примечание
Здесь трудно обойтись без специального метода установки обратного указа-теля, в нашем случае метода Set. Дело в том, что при создании документа надо сначала создать вложенные в него объекты других классов (вспомните правило: "C++ уважает гостей"). Но в этот момент им нельзя передать адрес документа, так как он еще не создан. В таких случаях поступают следующим образом. В заголовке конструктора документа создают пустые объекты (вызывают default-конструкторы вложенных объектов), а затем в теле конструктора документа, когда он уже существует, для вложенных объектов вызывают метод, устанавливающий обратный указатель. При этом объекту передают указатель на документ (на объект собственного класса). Например: m_Poly.Set(this);
Обилие методов класса CPolygon сделано "на вырост". Сейчас каждый документ для простоты представлен одним полигоном. Реальные конструкции можно задать в виде множества полигонов. При этом каждый из них должен знать свои габариты. Метод GetRect позволяет вычислять и корректировать габариты полигона. Если вы будете применять эти идеи в более сложном проекте, то вам понадобится множество других методов. Например, методы, определяющие факт самопересечения полигона или взаимного их пересечения.
Главными методами, которые реализуют концепцию архитектуры "документ – представление", являются Serialize и Draw. Метод Serialize позволяет общаться с файлами. Его особенность состоит в том, что он позволяет как записывать все данные объекта в файл, точнее в архив, так и читать их из файла. Здесь опять проявятся преимущества наследования от cobject, так как объекты классов, имеющих такого авторитетного родителя, обычно сами умеют себя сериализовывать.
Примечание
Термин "сериализация" приходится брать на вооружение, так как он довольно емкий, и чтобы его заменить, надо произнести довольно много слов о последовательном (in series) помещении данных объекта в архив, который связан с файлом. Кроме того, надо сказать о том, что в классе CArchive переопределены операции " и ". Просмотрите почти пустое тело функции Serialize в классе документа. Оно, тем не менее, намекает нам, как разделяются две разновидности общения с архивом. Вызов функции CArchive::IsStoring() возвращает ненулевое значение в случае, если архив используется для записи данных.
Новый класс CPolygon должен иметь родителя CObject, с тем чтобы он мог воспользоваться его мощным оружием – сериализацией. При этом в объявлении класса должен присутствовать макрос:
DECLARE_SERIAL(CPolygon)
Который влечет свое продолжение – другой макрос:
IMPLEMENT_SERIAL(CPolygon, CObject, 1)
Последний должен быть расположен в файле реализации класса. Третий параметр (wSchema) этой макроподстановки задает номер версии приложения. Номер схемы кодируется и помещается в архив вместе с другими сохраняемыми данными. Это позволяет корректно обойтись в такой ситуации.
Предположим, что имеются файлы с расширением mgn, в которых хранятся данные о магнитах, созданных нашим приложением. Затем допустим, что мы внесли изменения в коды приложения и добавили в класс CPolygon еще одно какое-то поле данных. Теперь, записывая данные в архив (файл), также получим файл с расширением mgn, но другого формата. После этого мы не сможем правильно читать старые файлы. Если не предпринять никаких мер, то данные будут прочитаны неверно, а это часто приводит к непредсказуемому поведению программы. Механизм версий справляется с этой проблемой, но вы не должны забывать вовремя менять номер версии. При каждом изменении в структуре сохраняемых данных следует изменять номер версии. При попытке прочитать файл, соответствующий другой версии, каркас приложения просто выдаст сообщение о несовпадении версий и закроет файл данных.
С учетом сказанного рассмотрим, как должна выглядеть реализация нового класса. Следующие функции и макросы необходимо поместить в начало файла TreeDoc.cpp, после директив препроцессора:
IMPLEMENT_SERIAL(CPolygon, CObject, 1) //====== Конструктор по умолчанию CPolygon::CPolygon() { m_pDoc = 0; // Пока не знаем обратного адреса MakeStar(); // Зададим полигон в виде звезды }