Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
PAGE 8
Министерство общего и профессионального образования
Российской Федерации
Тверской государственный технический университет
Кафедра электронных вычислительных машин
Программирование в среде Microsoft Visual Studio.
Разработка классов.
Освоение приемов работы с графическими объектами и манипуляции ими.
Методические указания к лабораторным работам
по курсу "Технология программирования"
для студентов 2-го курса специальности ВМКСС
Лабораторная работа № 3
Тверь 2011
Цель лабораторной работы заключается в приобретении навыков практической разработки классов, изучении механизма инкапсуляции и его отличительных особенностей, а также освоении возможностей технологии .NET и интегрированной среды Microsoft Visual Studio для разработки программных приложений при формировании и манипуляциях графическими объектами в виде геометрических фигур.
Основными задачами, решаемыми в процессе выполнения лабораторной работы, являются:
Методическое указание обсуждено на заседании кафедры ЭВМ (протокол №___от "__"___________ 2011 года) и рекомендовано к печати.
Составитель: Веселов А.А.
Содержание
№ п/п |
Раздел |
Стр. |
||
1. |
Общая часть |
4 |
||
1.1 |
Абстрагирование |
4 |
||
1.2 |
О пользе абстрагирования |
5 |
||
1.3 |
Принципы объектно-ориентированного программирования |
5 |
||
1.4 |
Объекты и классы |
6 |
||
2. |
Элементы класса и их особенности |
6 |
||
2.1 |
Состав класса |
7 |
||
2.2 |
Уровни доступа к членам класса |
7 |
||
2.3 |
Конструкторы |
8 |
||
2.4 |
Деструкторы |
8 |
||
2.5 |
Члены - данные |
8 |
||
2.6 |
Методы |
9 |
||
2.7 |
Указатель this |
10 |
||
3. |
Проектирование классов |
10 |
||
3.1 |
Этапы проектирования |
11 |
||
3.2 |
Общие рекомендации по проектированию классов |
11 |
||
3.3 |
Определение набора операций |
12 |
||
3.4 |
Определение зависимостей между классами |
12 |
||
4. |
Практические рекомендации по проектированию класса фигуры |
12 |
||
4.1 |
Проектирование класса многоугольника |
13 |
||
4.2 |
Изменение положения |
15 |
||
4.3 |
Изменение формы многоугольника |
17 |
||
5. |
Задание на лабораторную работу |
18 |
||
6. |
Рекомендуемый порядок выполнения работ |
19 |
||
7. |
Содержание отчета |
19 |
||
|
Литература |
20 |
1. Теоретическая часть
1.1. Абстрагирование
Абстрагирование заложено в самих языках программирования и связано с тем, как данная проблема представлена в пространстве программы. Большинство объектно-ориентированных языков отстраняют вас (абстрагируют) от множества мелких деталей и подробностей, мешающих разработчику увидеть самое существенное в объектах реального мира, позволяя при решении какой-либо прикладной задачи сосредоточиться только на главных особенностях.
При объявлении классов в объектно-ориентированных языках следует, прежде всего, использовать такие имена и интерфейсы, которые отражают смысл и назначение объектов предметной области. "Сокрытие" элементов, не связанных напрямую с решением задачи, позволяет полностью сосредоточиться на самой задаче и решать ее более эффективно. Другими словами, качественное решение проблемы сводится к качеству применяемого абстрагирования. Однако язык это только один уровень абстрагирования. Если пойти дальше, то, как разработчику класса, вам нужно придумать такую степень абстрагирования, чтобы клиенты вашего класса сразу же могли сосредоточиться на своей задаче, не тратя время на изучение работы класса. На очевидный вопрос какое отношение открытый интерфейс класса имеет к абстрагированию? можно ответить так: интерфейс класса это и есть реализация абстрагирования.
Для большей ясности воспользуемся аналогией с работой, которая происходит внутри торговых автоматов. Дело в том, что подробное описание такой работы довольно трудно. Чтобы выполнить свою задачу, автомат должен принять деньги, что-то рассчитать, дать сдачу и только после этого - выдать требуемый товар. Однако покупателям - пользователям автомата представляются для использования только несколько его элементов. Такими элементами автомата являются: щель для приема денег, кнопки выбора товара, рычаг для запроса сдачи, лоток, куда поступает сдача, и желоб подачи товара. Следует отметить тот факт, что со времени изобретения торговых автоматов их внешний почти не изменяется. Это связано с тем, что, несмотря на то, что, по мере развития технологии, их внутренняя организация и совершенствовалась, но основной интерфейс не нуждался в больших переменах. Это говорит о том, что достаточно глубокое понимание предметной области и ее особенностей помогает разработчику создать интерфейс, предоставляющий пользователям доступ к нужной им информации и методам, но изолирующий их от знания внутреннего устройства класса. При разработке интерфейса следует думать не только о решении текущей задачи, но и о том, чтобы обеспечить такое абстрагирование от внутреннего устройства класса, которое позволит неограниченно модифицировать (изменять) закрытые члены класса, не затрагивая его открытого интерфейса.
При определении нужной степени абстрагирования класса важно помнить и о программисте, который будет пользоваться этим классом. Например, кто-то разрабатывает программное обеспечение ядра базы данных. Возможно, что он прекрасно разбирается в таких понятиях базы данных, как курсоры, ключевые поля, транзакции, управление фиксацией и кортежи. Однако многие другие разработчики, не столь искушенные в программировании баз данных, не собираются (да им и не обязательно) вникать в тонкости этих понятий. В результате, использование терминологии, непонятной другим пользователям вашего класса, не позволяет достигнуть основной цели абстрагирования повысить эффективность работы программиста путем описания объектов предметной области в естественных и понятных ему терминах.
Кроме того, решая, какие члены класса сделать открытыми, надо опять помнить о возможных пользователях вашего класса. Это еще раз подтверждает необходимость иметь хотя бы начальное представление об особенностях объектов предметной области и других программистов, которые, возможно, будут использовать ваш класс. Так, в случае с базой данных другие программисты, использующие созданный класс, наверное, не должны будут иметь прямого доступа к членам, представляющим внутренние буферы данных этого класса. Ведь структура этих буферов может когда-нибудь измениться. Кроме того, от целостности этих буферов зависит вся работа ядра базы данных, и поэтому операции по их изменению следует выполнять только методами, которые специально предназначены для этого в самом классе. Только после этого можно сказать, что предприняты все меры предосторожности.
При разработке своих классов всегда необходимо ставить себя на место программиста, которому предстоит работать либо с экземплярами этих классов либо с производными от них классами.
1.2. О пользе абстрагирования
Взгляд программистов на создаваемые ими классы как на абстракцию, с которой максимально удобно работать, имеет первостепенное значение при разработке повторно используемого программного обеспечения. Если программист выстраивает интерфейс класса, на который не влияют последующие изменения в реализации класса, то создаваемым с их помощью приложениям долгое время не понадобятся никакие модификации. В этом смысле характерным примером является пример с торговым автоматом. Если программист хорошо знаком с предметной областью задачи, то он может легко определить состав методов, которые понадобятся пользователям его класса. Таким образом, если при проектировании класса удается сочетать хорошее знание предметной области с прогнозом относительно дальнейших перспектив использования класса, то можно будет гарантировать, что большая часть его интерфейса останется неизменной, даже в случае возможного последующего изменения реализации этого класса.
В результате отстранения пользователя от деталей реализации система в целом становится намного понятнее, а значит, и удобнее в работе.
1.3. Принципы объектно-ориентированного программирования
Изучая работу программистов, исследователи заметили, что они в течение определённого времени пишут и отлаживают приблизительно одинаковый по объёму код, независимо от используемого языка программирования. Объём работы приблизительно тот же, но результаты разные. Например, написание 100 строк кода на Си требует столько же затрат, сколько и 100 строк кода на ассемблере, но возможности кода на Си гораздо шире. Поняв это, исследователи начали разрабатывать языки высокого уровня, которые увеличивали бы производительность отдельного программиста, сокращая тем самым сроки и стоимость разработки проектов.
Вначале широкую популярность получили так называемые процедурные языки программирования. В процедурных языках, таких как Си и Pascal, программирование было ориентировано на операции или функции. При этом программисты сосредотачиваются на написание операций. Затем они группируют операции, выполняющие определенные задачи, в функцию. После чего такие функции группируются и образуют программу. Конечно, при таком стиле программирования данные тоже важны, но они существуют в основном для поддержки операций, выполняемых функциями. Требования к новому программному приложению служат программисту для определения набора функций, которые будут работать совместно для реализации всей системы.
Однако, еще в 70-х годах прошлого века среди создателей языков программирования особую популярность стала приобретать другой стиль программирования, ориентированный на использовании концепции не функции, а объекта. Объект представлялся как совокупность кода и данных, созданных для воспроизведения свойств физических предметов или абстрактных понятий. Как элементы программирования такие объекты оказались чрезвычайно эффективными, если они представляют собой прямую абстракцию обычно используемых предметов и в значительной степени скрывают сложность своей реализации от пользователей. Новый стиль программирования получил название объектно-ориентированного программирования (ООП).
При использовании ООП программисты сосредоточены на создании собственных типов, определяемых пользователем. Наиболее известной их разновидностью являются так называемые классы. Каждый класс содержит набор данных и функций для манипулирования этими данными. Данные класса называют переменными экземпляра класса, Таким образом, в ООП упор делается на классы, а не на функции. На основании требований к разрабатываемому приложению программист уже определяет не набор функций, а набор классов, с которого будет начинаться процесс проектирования. Программисты используют эти классы для создания объектов, которые будут работать совместно в рамках создаваемой им системы.
Так постепенно на смену процедурному программированию пришел объектно-ориентированный стиль программирования. Поскольку класс - это тип, который определяет сам пользователь, то это позволяет ему определять новые типы данных так, что их использование ничем не будет отличаться от использования встроенных в язык типов данных (таких, как: int, float, double и другие). При этом, программист может создавать нужные ему типы данных, в значительно большей мере соответствующие понятиям той прикладной области, для которой разрабатывается его программа, чем встроенные типы данных, которые ориентированы только на архитектуру конкретной ЭВМ. Классы обеспечивают скрытие данных, гарантированную инициализацию данных, неявное преобразование типов, динамическое задание типа, контролируемое пользователем управление памятью, механизмы перегрузки операций и многое другое.
Суммируя все сказанное можно утверждать, что объектно-ориентированное программирование (ООП) представляет собой методологию, которая концентрируется больше на связях между объектами, чем на деталях реализации. Такие связи обычно развиваются по принципу деревьев, при этом новые типы объектов образуются из уже существующих. Сокрытие реализации объекта ведёт к тому, что пользователя больше волнует связь с остальными объектами, чем поведение объекта. Данное отличие важно, поскольку оно означает принципиальный отказ от "процедурных" языков (таких как Си, Паскаль или Бэйсик), в которых основу деятельности программиста составляют функции и вызовы функций.
Есть разные трактовки термина "класс", показывающие, в частности, чем класс отличается от объекта. Обычно класс понимают как новый тип данных, определяемых пользователем, с которым тесно связаны некие методы. В таком случае объект это экземпляр этого класса. Класс удобно рассматривать как чертеж или схему будущего объекта. Как разработчик объекта, вы сначала создаете его "чертеж", так же как инженер-строитель проектирует план дома (или инженер-электронщик схему телевизора). Имея такой чертеж, вы располагаете всего лишь проектом дома этого типа. Однако те, кто располагает этим чертежом, могут по нему построить себе дом. Таким на базе класса можно создать объект, обладающий всеми возможностями этого класса.
2. Элементы класса и их особенности
Описание класса содержит ключевое слово class, за которым следует его имя, а далее в фигурных скобках тело класса, то есть список его элементов. Кроме того, для класса можно задать его базовые классы (предки) и ряд необязательных атрибутов и спецификаторов, определяющих различные характеристики класса:
[ атрибуты ] [ спецификаторы ] class имя_класса [ : предки ] { тело_класса }
Обязательными являются только ключевое слово class, имя и тело класса. Тело класса это список описаний его элементов, заключенный в фигурные скобки. Список может быть пустым, если класс не содержит ни одного элемента. Таким образом, простейшее описание класса может выглядеть так:
class Demo {}
Класс является обобщенным понятием, определяющим характеристики и поведение некоторого множества конкретных объектов этого класса, называемых экземплярами, или объектами, класса. Объекты создаются явным или неявным образом, то есть либо программистом, либо системой. Программист создает экземпляр класса с помощью операции new, например:
Demo a = new Demo(); // создание экземпляра класса Demo
Demo b = new Demo(); // создание еще одного экземпляра класса Demo
Каждый раз при создании экземпляра класса в памяти выделяется отдельная область, в которой хранятся значения данных, которые характеризуют состояние каждого из них. Кроме того, в классе могут присутствовать статические элементы, которые существуют в единственном экземпляре для всех объектов класса. Часто статические данные называют данными класса, а не статические данные данными экземпляра класса.
Функциональные элементы класса не тиражируются, то есть всегда хранятся в единственном экземпляре для всех объектов одного и того же класса. Для работы с данными класса используются методы класса (статические методы), а для работы с данными экземпляра методы экземпляра, или просто методы.
Поля и методы являются основными элементами класса. Кроме них, в классе можно задавать целую гамму и других элементов: свойства, события, индексаторы, операции, конструкторы и деструкторы.
2.1. Состав класса.
Класс, как и структура, представляет собой набор данных и функций, предназначенных для совместного выполнения определённой задачи. Как говорят, класс инкапсулирует задачу. Классы имеют следующие характерные элементы:
Как и любой другой тип данных, класс должен быть предварительно объявлен. Объявление класса обычно содержит перечень полей данных и методов. Как правило, для определения содержимого класса создают файл с именем, близким к имени класса.
2.2. Уровни доступа к членам класса.
Члены класса могут иметь три уровня доступа: закрытый (private), открытый (public) или защищённый (protected). Уровни доступа к членам класса определяют способ работы пользователей с классом. Программируя самостоятельно, программист может быть как создателем класса, так и их пользователем. При работе в команде один программист может быть создателем класса, а остальные - его пользователями.
Любой класс содержит открытую часть, к которой возможен доступ извне класса, и закрытую часть. Закрытая часть класса определяет его внутреннюю реализацию. В хорошо спроектированном классе от пользователя скрыто всё, что ему не требуется знать.
Абстрактное представление данных - это сокрытие внутренней реализации элементов класса от его внешнего окружения. Абстрактное представление данных даёт пользователю возможность знать о классе ровно столько, сколько необходимо, и предохраняет от вмешательства туда, куда вмешиваться не стоит. Но если внутренняя работа класса закрыта, то интерфейс пользователя должен быть открытым.
К защищённым членам класса, как и к закрытым, пользователь обращаться тоже не может. Однако эти члены могут быть доступными для классов, которые являются производными от данного класса.
2.3. Конструкторы.
Конструктор это функция, которая автоматически вызывается при создании экземпляра класса. Конструктор используется для инициализации переменных-членов класса, выделения необходимой памяти и выполнения других действий, необходимых перед началом использования класса. Необходимо помнить, что неинициализированные переменные содержат случайные значения. Это касается и членов данных класса. Поэтому для безопасности следует присваивать переменным - членам класса какие-либо начальные значения.
Конструктор, у которого нет аргументов, называется конструктором по умолчанию. Имя конструктора должно совпадать с именем класса. Это служит отличительным признаком конструктора. Конструктор не может возвращать никакого значения и поэтому для него не указывается тип возвращаемого значения.
Важно понимать, что нельзя явно вызывать конструктор. Конструктор используется только при создании объекта (или реализации) класса. Возможно использование любого количества конструкторов, но при этом нельзя допускать неоднозначности в списке параметров.
2.4. Деструкторы.
Деструктор - это специальная функция, которая автоматически вызывается перед уничтожением объекта. Обычно он используется для освобождения памяти, выделенной под экземпляр (объект) класса, или выполняет другие задачи по наведению порядка после работы класса. Класс может иметь не более одного деструктора. Наличие деструктора не является обязательным. Деструктор не возвращает никакого значения и не принимает никаких аргументов. Его имя образуется из имени класса с добавлением впереди знака тильды (~).
Как было сказано, деструктор вызывается непосредственно перед разрушением объекта класса. Объект класса может быть разрушен либо при выходе из области видимости, либо в результате применения оператора delete. В любом случае, вызов деструктора будет последним действием перед окончательным исчезновением объекта класса.
2.5. Члены - данные.
Члены - данные класса это просто переменные, объявленные в описании класса. Их областью видимости по умолчанию является класс. По сравнению с членами - данными структур, можно управлять доступом к членам-данным класса, объявляя их как private, public или protected. Независимо от установленного уровня доступа, члены-данные класса могут использоваться всеми функциями этого класса. Уровень доступа определяет видимость членов-данных вне класса. Для работы с закрытыми членами-данными можно написать и использовать открытые свойства, обеспечивающие доступ к этим закрытым данным. Одна из функций таких свойств будет возвращать значение соответствующего закрытого члена класса (функция чтения), а другая - изменять его значение (функция записи).
Использование в классе свойств даёт важное преимущество оно обеспечивает возможность контроля входных данных, что позволяет следить за изменением значений, которые принимают закрытые члены-данных класса.
Для каждого экземпляра класса в памяти создаётся своя отдельная копия членов-данных. Исключение делается только для членов-данных, объявленных как статические данные с модификатором static. В этом случае все экземпляры класса используют одну единственную копию соответствующего члена.
При работе членами-данными существуют некоторые рекомендации, заключающиеся в следующем:
2.6. Методы.
Методы-члены класса - это методы, принадлежащие классу. Они являются локальными по отношению к классу, в котором они определены, и не существуют вне класса. Функции-члены могут быть вызваны только изнутри класса. Они имеют доступ ко всем открытым, защищённым и закрытым членам-данным своего класса.
Открытые методы-члены класса представляют собой средство для организации взаимодействия (интерфейс) между пользователем и классом. Именно через открытые функции внешнее окружение класса получает доступ к тем возможностям, которые предоставляет класс.
Закрытые функции-члены предназначены только для использования самим классом. Их вызов пользователем класса не предполагается, они скрыты от внешнего мира. Для некоторых классов начальные процедуры инициализирующих действий могут потребовать выполнения кода довольно большого объёма. Для того, чтобы не загромождать им конструктор, лучше всего написать некоторую вспомогательную функцию, например, Init(), которая будет вызываться из конструктора и выполнять все необходимые действия. Такая функция никогда не должна напрямую вызываться пользователями класса и поэтому она должна быть закрытой.
Защищённые функции-члены - это функции, доступные только для тех классов, которые являются производными данного класса. Доступ извне к этим функциям невозможен.
Метод это функциональный элемент класса, который реализует вычисления или другие действия, выполняемые классом или экземпляром. Совокупность всех методов в классе полностью определяют его поведение.
Таким образом, метод представляет собой законченный фрагмент кода, к которому можно обратиться по имени. Он описывается один раз, а вызываться может столько раз, сколько необходимо. Один и тот же метод может обрабатывать различные данные, переданные ему в качестве аргументов.
Синтаксис метода:
[ атрибуты ] [ спецификаторы ] тип имя_метода ( [ параметры ] ) тело_метода
Первая строка представляет собой заголовок метода. Тело метода, задающее действия, выполняемые методом, чаще всего представляет собой блок.
Чаще всего для методов задается спецификатор доступа public, ведь методы составляют интерфейс класса то, с чем работает пользователь.
Пример простейшего метода:
public float Gety(float a, float b)
{
float sum = a+b;
return sum;
}
Тип метода определяет тип результата вычислений, осуществляемых этим методом. Наиболее часто употребляется термин «метод возвращает значение». Если метод не возвращает никакого значения, то в его заголовке задается тип void, а оператор return отсутствует.
Параметры или аргументы (в нашем примере это a и b) используются для обмена информацией с методом. Параметр представляет собой локальную переменную, которая при вызове метода принимает значение соответствующего входного аргумента. Область действия параметра весь метод.
Например, чтобы вычислить значение синуса для вещественной величины x, мы передаем ее в качестве аргумента в метод Sin класса Math, а чтобы вывести значение этой переменной на экран, мы передаем ее в метод WriteLine класса Console:
double x = 0.1;
double y = Math.Sin(x);
Console.WriteLine(x);
При этом, в приведенном фрагменте листинга, метод Sin возвращает в точку своего вызова вещественное значение синуса, которое присваивается переменной y, а метод WriteLine ничего не возвращает.
Следует заметить следующее, что метод, не возвращающий значение, вызывается отдельным оператором, а метод, возвращающий значение, в составе выражения в правой части оператора присваивания.
Параметры, описываемые в заголовке метода, определяют множество значений аргументов, которые можно передавать в метод. Для каждого параметра должны задаваться его тип и имя. Например, заголовок метода Sin выглядит следующим образом:
public static double Sin( double a );
Имя метода в совокупности с количеством, типами и спецификаторами его параметров представляет собой сигнатуру метода. В классе не должно быть методов с одинаковыми сигнатурами.
2.7. Скрытый указатель с именем this.
Все классы имеют скрытый член с именем this. Это указатель на экземпляр данного класса. Следует вспомнить, что каждый экземпляр класса имеет свою копию членов-данных, но использует один набор функций-членов, общий для всех экземпляров этого класса. Как же компилятор определяет, какой из его экземпляров вызвал функцию? Для этого каждая функция-член класса имеет скрытый аргумент this.
3. Проектирование классов.
При проектировании всегда следует учитывать тот факт, что этот процесс неизбежно будет расширяться, переноситься, перенастраиваться и, вообще изменяться множеством способов, которые невозможно предвидеть заранее. Поэтому необходимо стремиться к проектированию и разработке достаточно простых систем. Необходимость простоты объясняется тем, чтобы иметь в будущем возможность модифицировать (изменять) проектируемую систему в различных направлениях. Система должна проектироваться так, чтобы, после последовательности видоизменений, оставаться настолько простой, насколько это возможно. Поэтому, при проектировании системы следует всегда иметь в виду возможность ее неизбежных модификаций, и при этом обеспечить:
Лучше всего попытаться это осуществить, попытавшись инкапсулировать (соединить в одно целое) все части системы (которые, вероятно, будут изменяться) и обеспечить способы модификации кода. Этого можно достигнуть, выявляя в данной конкретной области ключевые понятия и возлагая обязанности по хранению, изменению и использованию всей информации о каждом понятии на соответствующий класс. В идеале такая модификация могла бы осуществляться путём наследования классом содержимого своих базовых классов.
3.1. Этапы проектирования.
Отметим, что любое понятие не существует изолировано, а, как правило, определяется на фоне других понятий. Точно также и класс не существует обособленно, а определяется вместе с логическими связанными с ним классами. Обычно разрабатывается сразу множество взаимосвязанных классов. Такое множество часто называют библиотекой классов.
Множество классов объединяются посредством некоторого логического критерия, или общего стиля, или часто некоторой опоры на общий сервис. Поскольку проектирование такого множества классов почти всегда вызывает определённые трудности, то рекомендуется придерживать следующей последовательности действий:
Необходимо отметить, что это только несколько шагов итеративного процесса. Обычно нужно повторить эту последовательность несколько раз, прежде, чем будет выработан проект, который в достаточной степени подходит для начальной, либо для повторной реализации.
3.2. Общие рекомендации по проектированию классов.
Один из известных разработчиков объектно-орентированных языков Бъярн Страуструп утверждает, что ключ к искусному проектированию можно подобрать, если непосредственно моделировать некоторые аспекты "окружающей действительности", то есть "поймать" понятия из данной прикладной области в виде классов, представить зависимости между классами формальным образом, например, в виде наследования или агрегирования, и проделывать эти операции повторно на разных уровнях абстракции.
Но как найти такие понятия и как определить - какие классы нам нужны? Лучше всего начинать с изучения самой прикладной области как множество абстракций и концепций. Особое внимание при этом следует обратить на используемый словарь терминов. Часто утверждают, что существительные соответствуют классам в программе. Глаголы могут означать операции над объектами. Часто в виде классов можно представить даже прилагательные. Такие прилагательные, как "хранимый", "согласованный", "зарегистрированный" можно представить в виде виртуальных базовых классов с той целью, чтобы разработчик мог отбирать из них наиболее подходящие атрибуты-классы и наделять (используя наследование) этими атрибутами классы, которые будут разрабатываться позднее.
Уточнять эти понятия на начальной стадии лучше всего путём обсуждения с экспертами в данной прикладной области и коллегами. Такое обсуждение необходимо для того, чтобы выработать жизнеспособный начальный словарь и концептуальную основу для дальнейшего движения. На уровне реализации классы могут также представлять системные ресурсы и другие абстракции. Взаимосвязи на данной стадии проектирования возникают естественным образом в результате понимания прикладной области, либо вследствие последующей работы над структурой класса..
3.3. Определение набора операций.
Как правило, при разработке классов основное внимание сосредотачивается на ключевых понятиях. При задании действий над классами, самым важным является поиск и нахождение наиболее полного и удобного набора операций. Очень трудно рассматривать обе эти стороны, принимая в расчёт то, что взаимосвязанные классы должны разрабатываться вместе.
Обычно используется следующая стратегия:
Гораздо легче добавить любую функцию, которая могла бы впоследствии оказаться полезной. Однако, чем больше будет функций, тем вероятнее то, что они будут оставаться неиспользованными и будут сдерживать как процесс самой разработки, так и его дальнейшего развития. При подборе операций важно сконцентрировать своё внимание на том, что нужно сделать, а не на том, как это будет реализовываться.
3.4. Определение зависимостей между классами.
При определении зависимости от других классов самыми важными являются отношения наследования и вложенности свойств и использования. Обе эти зависимости подразумевают анализ того, как класс может отвечать за некоторые свойства системы. Отвечать за свойство - вовсе не означает, что класс должен содержать все данные сам или его функции-члены должны непосредственно выполнять все необходимые операции. Наоборот, то, что каждый класс имеет только один ответственный участок, убеждает в том, что основная работа, выполняемая классом - это направлять запросы куда-нибудь в другое место для обработки другому классу, ответственному за эту подзадачу. Чрезмерное использование этой методики может привести к неэффективному и с трудом понимаемому проекту из-за увеличения числа объектов до момента, когда уже не осуществляется никакая работа, кроме каскада последовательных запросов на обслуживание.
Прежде чем начать конструировать класс многоугольника нужно попытаться представить себе - что представляет собой понятие многоугольника и попытаться выделить в нем наиболее важные элементы. В нашем случае понятие (абстрактное представление) многоугольника связано с некоторым графическим отображением такого объекта, который представляет собой множество вершин (точек), соединенных между собой отрезками прямых линий, называемых ребрами. Поэтому класс многоугольника должен содержать элемент, в котором будут храниться все его вершины. Кроме того, многоугольники могут быть раскрашенными в разные цвета и обводится контуром, цвет которого отличается от цвет закраски самой фигуры. Причем контуры фигуры (ее ребра) могут изображаться в виде сплошной, пунктирной, штрих-пунктирной и других типов линий. А закраска поля фигуры тоже может осуществляться с использованием разных стилей закраски. Некоторые (если не все) из фигур могут содержать текстовый комментарий (например: "Фигура №1", Фигура №2" и т.д.). Поэтому класс многоугольника, кроме своих вершин, должен содержать еще и переменные, определяющие цвет контура и цвет закраски, стиль изображения контурных линий и стиль закраски области внутри фигуры, текст комментария и т.д. В результате создается перечень полей данных, которыми может располагать класс.
А каким же интерфейсом должен обладать класс многоугольника? Прежде всего, это, наверное, методы, позволяющие добавлять и удалять его вершины, вырисовывать экземпляры класса, перемещать их, изменять положение выбранной вершины, изменять цвет контура или закраски и другие. Кроме того, следует учесть и возможные области использования этого класса. Например, использование класса многоугольника в приложениях для графического редактирования изображений, представляющих собой специфическую совокупность, связанных между собой отдельных многоугольников (деревенский дом с окнами и дверями, оградой и калиткой и т.д.). В таком случае, часто требуется выбрать одну из фигур, например, окно или дверь, и переместить ее в другое место. Но для этого нужно, чтобы фигура могла реагировать на попадание курсором в ее область. Поэтому, желательно включить в состав методов класса и такую функцию. Можно, конечно, придумать множество таких функций. Где же предел и как не перейти границы того, что вовсе не свойственно данному классу? В этом случае следует руководствовать тем, что методам доступно выполнение только таких действий, которым достаточно использовании данных, которыми располагает данный класс. В этом смысле класс должен быть самодостаточным и не требовать никакой дополнительной информации из своего окружения.
Подобный анализ характерных особенностей объектов предметной области позволяет программисту разработать класс, который позволяет достаточно полно описать понятие о многоугольнике как одной из возможных форм представления геометрических фигур.
4.1. Проектирование класса многоугольника.
Прежде всего, в классе фигуры необходимо предусмотреть наличие некоторого множества элементов, каждый из которых представляет собой структуру, состоящую, по крайней мере, из двух полей данных целого типа, например, X и Y, описывающих положение каждой вершины (точки). Подобная структура может выглядеть, например, так:
struct myPoint
{
public int X, // положение точки по горизонтали
Y; // положение точки по вертикали
}
Однако, наилучшим вариантом представляется использование уже готовой структуры, встроенной в язык С# под названием Point. Этот значительно более мощный тип, обладающий более широкими функциональными возможностями. Она содержит в себе следующие элементы:
public struct Point
{
// Конструкторы:
public Point(int dw);
public Point(Size sz);
public Point(int x, int y);
// Статические данные
public static readonly Point Empty;
// Статические методы
public static Point Add(Point pt, Size sz);
public static Point Ceiling(PointF value);
public static Point Round(PointF value);
public static Point Subtract(Point pt, Size sz);
public static Point Truncate(PointF value);
// Статические операторы
public static Point operator -(Point pt, Size sz);
public static bool operator !=(Point left, Point right);
public static Point operator +(Point pt, Size sz);
public static bool operator ==(Point left, Point right);
public static explicit operator Size(Point p);
public static implicit operator PointF(Point p);
// Свойства:
public bool IsEmpty { get; }
public int X { get; set; }
public int Y { get; set; }
// Собственные методы класса
public void Offset(Point p);
public void Offset(int dx, int dy);
// Переопределенные виртуальные методы базового класса
public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
}
// Листинг 1: Для класса, содержащего массив элементов точек
class Figura
{
// Данные, характеризующие свойства объектов данного класса
….
myPoint Array[30]; // Массив из 30-ти элементов типа myPoint
int PointsNum; // Реальное количество вершин у многоугольника
public:
Figura(); // Конструктор класса
void AddPoint(int x, int y); // Добавление новой вершины в фигуру
// Другие функции, определяющие поведение объектов данного класса
…..
}
или:
// Листинг 2: Для класса, содержащего список элементов точек
class Figura
{
// Данные, характеризующие свойства объектов данного класса
….
List<Point> PointList; // Список точек
public:
Figura(); // Конструктор класса
void AddPoint(int x, int y); // Добавление новой вершины в фигуру
// Другие функции, определяющие поведение объектов данного класса
….
}
В первом варианте класса фигуры используется представление точки в виде созданной нами структуры типа myPoint и их хранилища в виде массива из ограниченного количества элементов.
Во втором варианте класса фигуры используется встроенная в язык С# структура типа Point и хранилище вершин фигуры, представленное в виде списка.
На основании анализа понятия о многоугольнике, проведенного выше, можно предложить более детальный вариант реализации его класса:
class Figura
{
// Данные, характеризующие состояние объектов данного класса:
List<Point> PointList; // Список точек (вершин многоугольника))
Color bc, // Цвет контура
fc; // Цвет закраски
public:
Figura(); // Конструктор класса
// Свойства:
public BColor
{
set{ bc=value; }
get{ return bc; }
}
public FColor
{
set{ fc=value; }
get{ return fc; }
}
public void AddPoint(int x, int y); // Добавление новой вершины в фигуру
public void Draw(Graphics g){…}; // Рисование многоугольника
public void Move(int dx, int dy){…}; // Рисование многоугольника
// Определение попадания курсора в область фигуры
public bool IsInto(int xc, int yc){…};
}
4.2. Изменение положения.
При редактировании графических объектов часто возникает необходимость в непрерывном изменении их положения (перемещении) в пределах клиентной области окна приложения.
Для реализации эффекта перемещения существует несколько способов, работа которых основана либо на перерисовке всей клиентной части окна, либо только той ее части, в пределах которой осуществляется конкретное перемещение, либо основанное на использовании промежуточного буфера, позволяющее максимально уменьшить мелькание изображения перемещаемого объекта. По существу каждый из перечисленных способов основан на реализации следующей последовательности действий:
В результате таких манипуляций создается эффект плавного перемещения изображения графической картинки объекта вслед за курсором.
Первый способ является самым простым, но и наименее эффективном способом. При таком способе процесс движения объекта осуществляется в четыре этапа. На первом этапе определяется объект, который необходимо двигать. Затем определяется величина перемещения (второй этап). После чего, в соответствии с полученной величиной сдвига, определяется новое положение объекта (третий этап). И, наконец, на последнем этапе перерисовывают клиентную часть окна приложения или перемещаемый объект. В результате на каждом шаге перемещения полностью перерисовывается окно и все его содержимое. Но поскольку выполнение такой операции требуется определенного (конечного) времени, то это приводит к заметному мельканию изображения. По сути дела вместо стирания изображения объекта стирается изображение всей области окна.
Второй способ, по существу, представляет собой модификацию первого способа. Он заключается в том, что перерисовывается не все видимое поле, в пределах которого перемещается выделенный объект, а только та его часть, которую занимал объект в своем предыдущем положении и в котором он в настоящее время находится (рис.1).
Рис.1. Перемещение объекта путем перерисовки ограниченной области
клиентной части окна приложения
Как видно из рис.1, перерисовываемая область становится заметно меньше области клиентной части окна. А это позволяет сократить время на перемещение и, следовательно, снижает эффект мерцания изображения перемещаемого объекта.
Третий способ основан на том, что изображение области перемещения объекта вначале формируется в буфере и только после этого оно копируется на соответствующую клиентную часть окна. Поскольку копирование изображения осуществляется более быстро, чем процесс рисования, то это позволяет максимально снизить эффект мерцания и создавать эффект более плавного его перемещения. Трудность реализации этого способа заключается только в том, чтобы перерисовать изображение перемещаемого объекта, не испортив изображения остальных объектов или фоновой картинки рабочего поля. Для решения этой проблемы используют буфер, предназначенный только для того, чтобы хранить изображения участка экрана, который находится под перемещаемым объектом. В качестве такого буфера можно использовать компоненту PictureBox, сделав ее невидимой, или с помощью объектов типа System.Drawing.Image и метода DrawImage. Использование промежуточного буфера позволяет легко стирать старое изображение перемещаемого объект путем копирования изображения, хранимое в буфере, на соответствующую область окна. При реализации этого способа весь процесс обычно разбивают на три фазы: подготовительную (реакция на нажатие на левую клавишу мыши), собственно перемещения (реакция на перемещение мыши) и заключительную (отпускание клавиши мыши).
Подготовительная фаза. Суть ее заключается в следующем:
Фаза перемещения. Возможную последовательность действий можно представить в следующем виде:
Заключительная фаза. На этой фазе требуется выключить режим перемещения и, возможно, освободить память, выделенную под промежуточный буфер. Впрочем, наличие сборщика мусора, выполняющего эту операцию автоматически, позволяет эту операцию опустить.
4.3. Изменение формы многоугольника.
Один из наиболее простых и часто используемых методов изменения формы многоугольника, в частности изменение положения одной из его вершин заключается в следующем:
Подготовительная фаза.
Фаза корректировки положения вершины.
Заключительная фаза.
В результате создаётся эффект плавного изменения (вслед за курсором) положения корректируемой вершины многоугольника при одновременном и соответствующем изменении размеров и расположения смежных с ним сторон.
Примечание. При реализации рассмотренных выше методик редактирования положения и формы геометрических фигур, следует определить реализацию функций отклика видимой компоненты, расположенной на форме окна приложения(например, PaintBox), на события, поступающие от манипулятора типа "мыши", таких как: OnMouseDown, OnMouseUp и OnMouseMove.
Подготовительную фазу следует реализовать с помощью реакции на событие OnMouseDown, фазу редактирования - OnMouseMove и заключительную фазу - с помощью OnMouseUp.
5. Задание на лабораторную работу.
Разрабатываемое приложение должно обеспечивать формирование группы многоугольников на его рабочей поверхности, размеры которой превышают размеры клиентной области окна приложения.
Для этого необходимо выполнить следующее:
а) Разработать абстрактное представление такой разновидности геометрических объектов, которые известны нам как многоугольники, в виде класса, позволяющего имитировать характеристики и поведение данного понятия (см. содержимое разделов 1,2 и 4.1).
б) На основе разработанного класса многоугольника построить интерфейс приложения, предназначенного для создания и редактирования многоугольных фигур и обеспечивающего следующие возможности:
- создавать новые фигуры в виде многоугольников с произвольным количеством вершин;
- перемещать фигуры в переделах клиентной части окна приложения тремя способами, описанными в разделе 4.2.
- изменять форму выбранного многоугольника путем изменения положения любой из его вершин (раздел 4.3);
- добавлять и удалять вершины у выбранного многоугольника;
- раскрашивать фигуры и их контуры в произвольные цвета и с использованием различных стилей.
в) Поскольку основная работа приложения происходит в результате его ответной реакции на события нажатия на клавишу "мыши" и перемещение "мыши", то необходимо разработать алгоритмы функционирования приложения реагирующего на эти два события.
г) На основе созданных алгоритмов и класса многоугольника разработать программное приложение для редактирования фигур.
д) Сравнить между собой эффективность различных способов перемещения многоугольников и изменения их формы.
е) В соответствии с разделом 7 составить пояснительную записку в форме отчета по лабораторной работе
6. Рекомендуемый порядок выполнения работ
При выполнении работы рекомендуется придерживаться следующего порядка:
7. Содержание отчета.
Отчет по лабораторной работе должен содержать:
Литература:
1. Биллиг В.А. Основы программирования на C#. © INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование, 2003-2011, 220 с.
2. Павловская Т.А. С#. Программирование на языке высокого уровня. Учебник для вузов. СПб.: Питер, 2007. 432 с.
3. Шилдт Г. С#. Учебный курс. СПб, Питер. 2003. 471 с.
4. Троелсен Э. Язык программирования C# 2010 и платформа .NET 4.0, 5-е изд.: Пер. с англ. М.: ООО "И.Д. Вильямс", 2011. 1392 с.: - Парал. Тит. Англ.
5. Дейтель Х. С#: Пер. с англ. / СПб.: БХВ-Петербург, 2006. 1056 с.: ил.
Новое положение объекта
Предыдущее положение объекта
Перерисовываемая область
Х
У