Объектно-ориентированное программирование |
Глава 2. Классы и объекты |
Введем обозначения: ОО – объектно-ориентированное(ая, ый, ых, …); ООМ – объектно-ориентированная методология; ООП – объектно-ориентированное программирование. По определению признанного авторитета в области ОО методов разработки программ Гради Буча: “ООП – это методология программирования, которая основана на представлении программы в виде совокупности объектов, каждый из которых является реализацией определённого класса (типа особого вида), а классы образуют иерархию на принципах наследуемости.” Когда Вы уже немного освоитесь в ООП, рекомендую еще раз прочитать это определение, чтобы убедиться насколько оно точно его характеризует. ООМ преследует те же цели, что и структурная методология: - дисциплинировать процесс программирования; - упростить разработку больших программных комплексов, что влечет за собой уменьшение вероятности ошибок, ускорение процесса разработки и, в результате, снижение стоимости готового продукта. Однако ООМ в большинстве случаев позволяет управлять более сложными проектами. ООМ направлена на создание интерфейсных систем, создание больших диалоговых программных комплексов. На практике, обычно, при разработке крупных проектов применяют оба подхода: сначала объектно-ориентированный, затем – структурный. Объектно-ориентированная модель – это описание как самих объектов, так и способа их взаимодействия.
В терминах Паскаля, объекты считаются близкими к типу Запись (Record). Как и запись, тип Объект (Object) является комбинированным типом (т.е. объединяет в себе элементы (поля) разных типов). Например, в отличие от массива, который включает элементы одного типа. Кстати, вспомнить про Запись можно посмотрев лаб.работу №5 и соответствующий раздел лекций по дисциплине “Программирование на языках высокого уровня, ч.1”. Однако структура типа Object является более совершенной структурой данных.
Основные отличия типов Record и Object:
Например,
Обратите внимание: в типе Запись после перечисления всех полей перед закрывающим словом end знак tRec = record В типе Объект описание всех полей обязательно должно заканчиваться знаком ; (даже если это последнее поле перед end;), иначе компилятор (программа, которая переводит текст программы в машинные коды) выдаст ошибку.Например, В описании объекта располагается только заголовок метода, а само тело метода пишется в разделе описаний до начала основной программы. О методах подробнее поговорим позже. Свойство (способность) объекта объединять в себе поля и методы называют инкапсуляцией. Определение. Объект – это объединение полей данных различных типов и методов обработки этих данных, представленных в виде процедур и функций.Рассмотрим пример. Пример 2.1 Пусть в дальнейшем нам необходимо написать ОО программу, работающую с графическими объектами (рисует, перемещает). В данном примере рассмотрим и опишем два объекта: некий абстрактный объект – фигуру и реальный объект – точку: tLocation – фигура; tPoint – точка. Что есть общего у всех фигур? Пусть, например, это будет центр фигуры – координаты x, y. К объекту точка добавим, кроме координат, еще и цвет (color). Давайте теперь разберемся с терминологией, т.е. что и как мы в дальнейшем будем называть. В литературе используется две терминологии:
Таким образом, если в какой-либо литературе Вы встретили термин “экземпляр объекта”, то должны понимать, что используется терминология 1. Это очень важно. Давайте далее будем придерживаться терминологии 2. Еще раз разберем термины на нашем примере 2.1: tLocation, tPoint – классы Loc, pt – объекты классов: Loc – объект класса tLocation; pt – объект класса tPoint; Класс существует в единичном экземпляре, а объектов какого-либо класса может быть сколько угодно. Если сравнивать с уже знакомой Вам терминологией из Паскаля, то класс – это тип (например, integer), а объект – это переменная данного типа (которых в программе можно объявить столько, сколько нужно).
Класс может наследовать компоненты (элементы) из другого класса. Наследующий класс (дочерний) называется потомком, а наследуемый класс (родительский) – предком. ! Процедура наследования относится только к классам, но не к объектам. Потомки содержат все поля и методы предков. При создании нового класса описываются только новые поля и методы. Если в родительском классе поле изменено, то у потомков это поле тоже изменено. Ни у одного потомка не должно быть полей, имена которых совпадают с именами полей любого из предков. Имена методов могут совпадать. Изменим пример 2.1. , применив принцип наследования.
Пример. 2.2
Что мы сделали? Мы объявили класс tPoint наследником класса tLocation. Для этого достаточно указать после слова object в круглых скобках имя родительского класса. Что нам это дало? Теперь все поля (и методы, но в нашем примере их нет) родительского класса унаследовались дочерним классом, т.е., хотя в классе tPoint поля x и y не описаны явно, но эти поля в данном классе есть. Кстати, обратите еще раз внимание на принятую нами терминологию. Давайте теперь представим (грубо) как будут храниться в памяти объекты (переменные) Loc и pt, и что в их полях будет записано после выполнения нашей программы:
Принцип наследования является основным отличием ООП от структурного программирования. Построение новых классов, наследующих все компоненты уже известных, может продолжаться бесконечно. В результате получается иерархия классов. Что в этом хорошего? Любой программист начинает создавать программу не “с нуля”, а использует уже существующие, кем-то написанные ранее, полностью готовые к работе классы. (Класс, обычно, описывается в отдельном модуле и является самодостаточным готовым программным продуктом). Если программист не находит такой класс, который бы полностью удовлетворял требованиям поставленной задачи, то он может создать свой (новый) класс, но опять же не “с нуля”, а дополнив необходимым уже существующие классы. Для этого надо просто объявить свой класс наследником и добавить все необходимое. Создавая программы таким образом, т.е. используя и дополняя уже готовые отлаженные программные заготовки, на разработку программы затрачивается гораздо меньше времени, возникает меньше ошибок, а это все приводит к снижению стоимости программных продуктов.Можно построить, например, такую иерархию графических объектов:
В лабораторных работах по данной дисциплине Вы как раз и будете работать с подобной иерархией классов. Классы, также как и другие типы, константы и переменные рекомендуется называть осмысленно. Так, например, Фигура – класс tLocation Точка – класс tPoint Эллипс – класс tEllipse Прямоугольник – класс tRect (на английском прямоугольник - Rectangel) Иерархия строится от общего к частному. В данной иерархии общее – центр фигуры, координаты точки x, y, т.к. любую фигуру можно построить через центр. Классы, не имеющие возможности иметь конкретные объекты, называются абстрактными (это классы верхних уровней иерархии). Методы этих классов также являются абстрактными. Прежде, чем перейти к следующему параграфу, еще раз перечислим правила наследования. Правило 1. Поля и методы родительского класса наследуются всеми его дочерними классами независимо от числа промежуточных уровней иерархии. Правило 2. Доступ к полям и методам родительских классов в рамках описания любых дочерних классов выполняется так, как будто-бы они описаны в самом дочернем классе. Отсюда следует Правило 3. Правило 3. Ни в одном из дочерних классов не могут использоваться идентификаторы (имена) полей, совпадающие с идентификаторами полей какого-либо из родительских классов. Это же относится и к идентификаторам формальных параметров, указанным в заголовках методов. Правило 4. Дочерний класс может доопределить произвольное число собственных методов и полей. Правило 5. Любое изменение текста в родительском методе автоматически оказывает влияние на все методы порожденных дочерних классов, которые его вызывают. Правило 6. В противоположность полям (см. правило 3) идентификаторы методов в дочерних классах могут совпадать с именами методов в родительских классах. В этом случае говорят, что дочерний метод подавляет одноименный ему родительский, и в рамках дочернего класса, при указании имени такого метода, будет вызываться именно дочерний метод, а не родительский. Однако возможность обращения к подавленному родительскому методу остается с помощью указания перед именем метода (через точку) идентификатора родительского класса. Например, Хороший стиль работы с классами исключает прямое обращение к полям. Для работы с полями используют методы. Метод – это процедура или функция, объявленная внутри класса. (см. далее пример 2.2.1) В описании класса располагается только заголовок метода: procedure (или function) имя метода …; Объявление первого метода внутри класса располагают после описания всех полей (т.е. сначала надо описать все поля класса, а только затем перечислить заголовки методов, включенных в данный класс). Тело метода можно описывать сразу после описания своего класса или после описания всех классов (предпочтительнее). Т.е. лучше сначала описать все классы (чтобы нагляднее прослеживалась иерархия, т.е. кто – родители, кто - наследники), а затем уже начать описывать сами методы: сначала все методы одного класса, затем – другого и т.д. При описании метода перед его именем необходимо указывать имя класса, к которому он относится (т.е. в котором указан заголовок данного метода): procedure (или function) имя класса.имя метода …; Классы и их методы имеют общую область действия в памяти, поэтому в конкретном классе имена формальных параметров методов и имена полей не должны совпадать. Напоминаю, что формальные параметры – это параметры, которые указаны в круглых скобках после имени метода (процедуры или функции) при его описании, а фактические параметры - которые указаны в круглых скобках после имени метода при его вызове. Недостатком примера 2.2 является обращение к полям напрямую. Исправим это, добавив метод инициализации полей в класс tLocation. Пример 2.2.1 Но и в примере 2.2.1 есть обращение напрямую к полю Color класса tPoint: pt.Color:=15; . Исправим и это: Пример 2.2.2 Если в дочернем классе объявлен метод с таким же именем, как и в одном из родительских классов, то говорят, что этот метод перекрывает или подавляет родительский (см. выше Правило 6 наследования). Перекрытие – это объявление в дочернем классе метода с таким же именем, как и в одном из родительских, но с другим содержанием. Как мы уже говорили ранее, остается возможность использовать (вызывать) методы родительского класса из методов дочернего класса. Покажем, как это сделать, дополнив Пример 2.2.2: Пример 2.3 В чем разница примеров 2.2.2 и 2.3? В том, что мы использовали то, что ранее уже было написано и проверено. А именно, вместо того, чтобы снова переписывать весь код проверенного и отлаженного метода tLocation.Init, мы его просто вызываем (в методе tPoint.Init), уже не задумываясь об его содержании. Таким образом, мы: - ускорили разработку программы; - сократили вероятность ошибок. Конечно, на данном примере эти преимущества не особенно заметны. Но представьте, если код метода tLocation.Init состоял бы, например, из 100 команд. Порядок вызова методов. Когда в программе встречается вызов какого-либо метода, то система осуществляет его поиск (снизу вверх по иерархии) следующим образом: сначала ищет в том классе, объект которого этот метод вызывает: если находит – выполняет, если нет – продолжает поиск метода в родительском классе и так далее вверх по иерархии. Если метод так и не был найден, то выдаётся соответствующее сообщение об ошибке. При описании метода указывается <имя класса>.<имя метода>; при вызове метода указывается <имя объекта>.<имя метода>.
Говорят, методы инкапсулированы (встроены) в описание класса. Инкапсуляцию и наследование мы уже рассмотрели, полиморфизм рассмотрим несколько позже. Хотя перекрытие методов тоже можно считать одним из проявлений полиморфизма.
Рассмотрим подробнее особенности наследования методов. Для этого вернёмся к примеру 2.3 и дополним программу движением по экрану точки и эллипса. Для этого в программу надо добавить:
Алгоритм движения фигур по экрану следующий: - рисуем фигуру цветом фона экрана (т.е. стираем фигуру на “старом” месте экрана); - изменяем координаты (можно изменять координаты центра фигуры, а затем рисовать их через центр); - рисуем фигуру заданным цветом (фигура появляется на новом месте экрана). Если повторить эти действия несколько раз, то будет складываться впечатление, что фигура двигается по экрану. Теперь запишем как, например, может выглядеть приведенный алгоритм движения на Паскале: Возможная реализация метода Move: Begin Или так: Begin Итак, перепишем пример 2.3. с учетом новых требований к нашей программе. В описание классов надо будет добавить не только метод Move, но и все другие методы, которые используются (т.е. в нашем случае еще Hide и Draw). Пример 2.4 Следует заметить, что приведенные в лекциях примеры не являются готовыми к работе программами. Например, чтобы что-то нарисовать, надо подключить модуль Graph, установить графический режим – это Вам известно из дисциплины ПЯВУ (лаб.раб. №4). Можете, кстати попробовать. Но сейчас нам это не важно – нам главное разобрать основные моменты работы программы. Как после компиляции программы компилятор разместит объекты pt и pEl в памяти можно изобразить следующим образом: Назовем область памяти, отведенной под объект pt – структурой 1, а под объект pEl – структурой 2. И еще, не путайте, мы сейчас говорим, что будет после компиляции программы, т.е. после перевода ее в машинные коды. Т.е. программа еще не работала, поэтому ячейки полей изображаем пустыми. Вместо имен методов уже проставлены их точные адреса – еще до выполнения программы. Вот это для нас важно. Структура 1.
Структура 2.
Методы Move после компиляции выглядят примерно так (только все это записано в машинных кодах): procedure tPoint.Move(NewX, NewY: integer); procedure tEllipse.Move(NewX, NewY: integer); Все описанные в примере методы называют статическими. Определение: Статическими называют методы, адреса которых связываются с объектами во время компиляции программы и не изменяются до завершения работы программы. Рассмотрим далее некоторые тонкости реализации Нетрудно заметить, что метод Move является идентичным для всех классов, т.к. алгоритм движения, реализованный в этом методе, является общим для любой фигуры. Поэтому возникает естественное желание сократить текст программы (особенно если мы соберемся добавить в программу описание еще нескольких классов), убрав объявление метода Move из класса tEllipse. Давайте попробуем это сделать. Получим следующее: Пример 2.5 Еще раз изобразим как после компиляции программы компилятор разместит объекты pt и pEl в памяти: Структура 3.
Структура 4.
Сравните структуры 1 и 3, 2 и 4. Что изменилось? Изменился только один адрес в структуре 4 – адрес метода Move. Теперь с объектом pEl связан адрес метода Move – tPoint.Move (прочитайте еще раз определение статических методов). Кстати, Вы поняли почему мы убрали объявление метода Move из класса tEllipsе, а компилятор все равно в объекте данного класса отводит место и записывает адрес метода Move? Ответ: потому что в классе tEllipsе есть метод Move, просто он теперь объявлен не в самом классе, а наследуется от родителя (в нашем примере - от tPoint). Метод Move после компиляции теперь выглядит примерно так (но только в машинных кодах): procedure tPoint.Move(NewX, NewY: integer); А метода procedure tEllipse.Move(); теперь нет. И если объект класса tEllipse вызовет данный метод (pEl.Move();) – то вызовется procedure tPoint.Move(); Давайте подробнее рассмотрим команды pt.Move(56,120); и pEl.Move(63,45); из примера 2.5. Выполнение данных команд повлечет за собой следующие цепочки вызовов: Т.е. движение произойдет после выполнения обеих команд, но двигаться и в том и в другом случае будет точка. А почему? Потому что непосредственно методы рисования (Hide и Draw) не вызываются в основной программе (в разделе описании действий). Для того чтобы передвинуть фигуру, в основной программе объектами разных классов вызывается метод Move класса tPoint. А как мы уже говорили ранее в результате компиляции (еще до выполнения программы) внутри метода Move уже жестко проставлены адреса всех используемых в нем методов. Теперь вернитесь чуть выше и посмотрите, как выглядит метод Move после компиляции и Вам станет ясно, почему всегда выполняются методы tPoint.Hide; и tPoint.Draw;, а методы tEllipse.Hide; и tEllipse.Draw; никогда не выполняются, даже если метод Move вызван объектом класса tEllipse. Существует такое правило наследования методов: Если наследуемый родительский метод вызывает еще и другие методы, то вызываться уже будут только родительские или вышележащие методы, так как вызовы методов из нижележащих по иерархии классов не допускаются. Действие данного правила мы и увидели на нашем примере. Возникает задача: как сделать метод Move общим (т.е. описать только в одном родительском классе), но при этом, чтобы всё работало правильно, то есть, чтобы двигалась фигура, соответствующая объекту, вызывающему метод Move. Т.е. нам надо, чтобы после компиляции (т.е. до выполнения программы) в методе Move на месте вызовов методов Hide и Draw не были записаны какие-либо конкретные адреса. Нам надо, чтобы в процессе работы программы, при вызове методов Hide и Draw из метода Move программа определяла бы объект какого класса вызвал метод Move и выполняла бы методы Hide и Draw этого же класса. Для этого нужен какой-то специальный механизм. Для решения поставленной задачи требуются методы, которые связываются с объектами не статически, то есть в процессе компиляции программы, а динамически, то есть в процессе выполнения программы. Дадим определения двум механизмам ООП: 1. Раннее связывание – процесс статического связывания методов с объектом. 2. Позднее связывание – процесс динамического связывания методов с объектом. Позднее связывание – это принципиально новый механизм, который обеспечивает полиморфизм, т.е. разный способ поведения для разных, но однородных в смысле наследования объектов. Полиморфизм – это множественность форм, т.е. когда один и тот же метод может принимать различные формы.
§7. Виртуальные методы. Конструкторы. Итак, как мы выяснили выше, существуют так называемые статические методы, реализующие механизм раннего (статического) связывания методов с объектом. Рассмотрим теперь методы, которые реализуют позднее связывание, т.е. которые связываются с объектом динамически (в процессе выполнения программы). Это так называемые виртуальные методы. Объявление виртуального метода: Procedure имя метода ( ); virtual; Т.е., чтобы сказать программе, что данный метод Вы хотите сделать виртуальным, Вам надо при объявлении метода внутри класса после знака ; написать слово virtual; (это слово называют директивой компилятора).Директива – это некоторое указание компилятору, как действовать дальше. Директив существует множество. В процессе компиляции программы, если компилятор встречает какую-либо директиву – он знает как действовать дальше (реакция на каждую директиву уже запрограммирована в системе). Программисту важно знать, что получиться в результате этих действий, а как и что компилятор делает для получения нужного результата – не важно. Правило: Если в каком-либо родительском классе объявлен виртуальный метод, то это накладывает следующие ограничения на все его дочерние классы. – все наследники виртуального метода должны быть тоже виртуальными; (статические методы не могут перекрывать виртуальные) – заголовки всех реализаций (вниз по иерархии) одноименных виртуальных методов должны быть одинаковыми (имена, количество и тип параметров); – каждый класс, в котором объявлен виртуальный метод, должен содержать конструктор. Конструктор – является специального типа процедурой, которая выполняет начальные установки для работы механизма виртуальных методов. Другими словами, - это метод специального типа, который служит для обеспечения работы с виртуальными методами. Сам конструктор является виртуальным методом, но директива virtual при его объявлении не указывается. Объявление конструктора: Constructor имя метода ( ); Замечание №1 Перед вызовом виртуального метода необходимо вызвать конструктор. Иначе механизм позднего связывания работать не будет, т.е. не произойдёт связи объекта с виртуальным методом. Вызов виртуального метода без предварительного вызова конструктора может привести к блокированию работы программы. Замечание №2 Конструктор может содержать любые команды. Тело конструктора может быть и пустым. Тогда при его вызове конструктор просто выполнит свои системные функции (свяжет объект с нужным виртуальным методом). Замечание №3 Каждый отдельный объект должен быть инициализирован отдельным вызовом конструктора. Например, в классе tPoint заменим процедуру Init конструктором: Constructor Init(InitX, InitY:integer; col: word); В основной программе: var a,b:tPoint; В этом случае конструктор выполнит и свои системные функции, и то, что мы напишем в теле конструктора (т.е. в данном примере выполнит то же, что делала procedure Init).Часто так и делают: за конструктор принимают метод инициализации полей объекта: например, Init. Это удобно, так как конструктор надо вызывать до вызова других (виртуальных) методов, а инициализацию полей объекта тоже, естественно, надо выполнять до вызова методов. Таким образом, вызвав конструктор перед работой всех методов, мы обеспечим и инициализацию объекта, и возможность работать с виртуальными методами. А для нас, как для программистов, вся разница будет заключаться только в том, что мы при объявлении и описании тела метода будем писать не procedure, а слово – constructor. Остальное – это дело компилятора.Замечание №4 Конструктор наследуется как обычный статический метод. Заголовок конструктора любого дочернего класса не должен совпадать с заголовком конструктора родительского класса. Работа с конструктором аналогична работе с другими методами. Основное назначение конструктора можно объяснить так: установка связи (поздней) объекта с виртуальными методами своего класса. Исправим пример 2.5, используя виртуальные методы. Пример 2.6. Наши действия: 1. Методы Hide и Draw сделаем виртуальными. Чтобы вспомнить, зачем нам это надо, вернитесь к § 6 и прочитайте в конце постановку задачи. 2. Метод Move вынесем в родительский класс tLocation. 3. Уберём метод Move из класса tPoint. 4. Сделаем процедуру Init – конструктором. Program Pr2_6; Проанализируем работу данной программы так же, как мы это делали с программой из предыдущего примера 2.5.
Изобразим как после компиляции программы компилятор разместит объекты pt и pEl в памяти: Структура 5.
Структура 6.
Заметим, что для виртуальных методов статически (в процессе компиляции) только выделяется память для адресов, а сами адреса будут занесены в ячейки в процессе установления поздней связи при вызове конструктора (в нашем примере, при вызове pt.Init(34,48,8); и pEl.Init(106,85,4,15,25);). После выполнения операторов pt.Init(34,48,8); и pEl.Init(106,85,4,15,25); структуры соответствующих объектов примут такой вид: Структура 7.
Структура 8.
Метод tLocation.Move после компиляции теперь выглядит примерно так: procedure tLocation.Move(NewX, NewY: integer); Как видно из рисунка (см. структуры 7 и 8), в объекте находятся адреса тех же самых методов, что и в рассмотренном ошибочном случае в примере 2.5. Однако теперь, благодаря виртуальности методов Hide и Draw, в результате поздней связи метод tLocation.Move автоматически настраивается на вызов методов Hide и Draw из того класса, объект которого их вызывает. Следует оговориться, что изменился еще один адрес: вместо tPoint.Move – tLocation.Move. Но это ни на что не влияет, можно было бы и оставить метод Move в классе tPoint. Просто так как-то логичнее – вынести общий для всех фигур метод в общий абстрактный класс. Кстати, класс tLocation – абстрактный. Абстрактными называют классы, которые не могут иметь конкретных объектов. Например, не может быть объекта фигура, может быть квадрат, круг, эллипс и т.д.. В абстрактный класс tLocation можно, например, включить всю информацию (в виде полей) и все действия (в виде методов), которые может содержать и проделывать фигура. Методы можно оставлять пустыми (как в примере 2.6., например, в классе tLocation тела методов Hide и Draw). Затем в дочерних классах методы можно перекрыть и написать необходимые действия. Давайте подробнее рассмотрим команды pt.Move(56,120); и pEl.Move(63,45); из примера 2.6.. Цепочки вызовов методов теперь снова будут правильными, как в примере 2.4.: Но теперь, в отличие от примера 2.4., в нашей программе используются виртуальные методы, которые приводят в действие одно из наиболее эффективных средств ООП – полиморфизм. Метод tLocation.Move является полиморфическим, т.к. в нем присутствуют заменяемые части (методы Hide и Draw), и поэтому он может подстраиваться под вызывающий его объект – это есть проявление полиморфизма, т.е. множественность форм одного и того же метода.
Заключение. Положительные стороны использования статических методов:
Положительные стороны виртуальных методов:
Основной принцип совместимости классов: совместимость распространяется от потомков к предкам, т.е. объекты дочерних классов могут свободно использоваться вместо объектов родительских классов, но не наоборот. Кроме того, любой дочерний класс наследует совместимость всех своих родительских классов. Рассмотрим совместимость между объектами и между формальными и фактическими параметрами методов. Совместимость между объектами. !!! Объекту родительского класса можно присвоить любой объект из дочерних классов, но не наоборот. Если разрешить присваивание наоборот, то те поля данных, которые были добавлены в дочерние классы, останутся незаполненными. Например, Var Loc: tLocation; Чтобы можно было хранить объекты разных однородных классов в одном массиве, то тип массива должен быть типом самого верхнего родительского класса, к которому принадлежит какой-либо из хранимых объектов. Совместимость формальных и фактических параметров. Формальный параметр данного класса может принимать в качестве фактических значений либо объект из своего класса, либо из любого дочернего. Например, procedure Show (t: tPoint);
Модули предназначены для поддержки принципов модульного программирования при разработке программ, основным из которых является принцип скрытия информации (information hiding). Согласно этому принципу взаимовлияние логически независимых фрагментов программы должно быть сведено к минимуму. Принцип скрытия информации, поддерживаемый модулями, позволяет создавать надежно работающие и легко модифицируемые программы. Модули используют для создания библиотек процедур, функций и классов, которые затем могут использоваться в программах, разрабатываемых пользователем. Синтаксис модуля имеет следующий вид: Unit Идентификатор модуля;{ Интерфейсный раздел } Interface {в этом разделе показано взаимодействие модуля с другими модулями, а также с главной программой. Другими словами – взаимодействие модуля с “внешним миром”}.
{ Раздел реализации } Implementation {в этом разделе указывается реализационная (личная) часть описаний данного модуля, которая не доступна другим модулям и программам. Другими словами – “внутренняя кухня” модуля }. { Список импорта раздела реализации } uses {В этом списке через запятые перечисляются идентификаторы модулей, информация интерфейсных частей которых должна быть доступна в данном модуле.} {Данный раздел может отсутствовать } { Подразделы внутренних для модуля описаний } label const type var procedure function {любой из подразделов может отсутствовать}
{ Раздел инициализации } begin {В этом разделе можно размещать операторы начальных установок (например, задать начальные значения переменных), необходимых для корректной работы модуля. Указанные здесь операторы выполняются при начальном запуске программы, к которой подключен данный модуль. Если никакой инициализации для данного модуля не требуется, то ключевое слово begin может быть опущено (end. - остается) } end. После написания текста модуля необходимо выполнить следующее: 1) сохранить модуль в файле с точно таким же именем, которым Вы назвали сам модуль в тексте после слова Unit (т.е. выполнить команду F2 - Save).2) из главного меню Паскаля выполнить команду Compaile Destination По умолчанию установлено Destination Memory, а надо, чтобы было установлено Destination Disk. Чтобы эти установки менять, достаточно просто щелкать мышью по данной команде. После каждого очередного щелчка значение меняется с Memory на Disk и наоборот. 3) откомпилировать модуль ( как обычную программу):из главного меню Паскаля выполнить команды (или сразу нажать F9)Compaile ![]() Compaile Alt-F9 или Make F9 (Запустить модуль на выполнение Ctrl-F9 не получиться.) После проделанных действий создастся файл имя.TPU в текущем каталоге. Т.е. в текущем каталоге Вы увидите теперь два файла с одинаковым именем, но с разным расширением: имя.PAS и имя.TPU. С этого момента Ваш модуль готов к использованию в программах и других модулях. Можно изменить каталог, в котором будет сохраняться файл имя.TPU. Для этого перед компиляцией модуля из главного меню Паскаля выполните команду Options Directories… В появившемся окне в строке EXE & TPU directory: укажите полный путь до каталога, в котором планируете сохранять файл имя.TPU . Как подключить какой-либо модуль к программе? Так же, как Вы подключали стандартные модули Graph, Crt в лаб. работах в курсе ПЯВУ. Для этого достаточно указать в программе: uses имя модуля1 [, имя модуля2, …]; Чтобы создать EXE – файл Вашей программы (т.е. исполняемый файл, который запускается вне системы Турбо Паскаль - сразу из проводника) надо проделать почти те же действия, что и при создании TPU – файла, а именно: установить Destination Disk, откомпилировать программу командой F9 и запустить на выполнение (Ctrl-F9). В текущем каталоге Вы увидите теперь два файла с одинаковым именем, но с разным расширением: имя.PAS и имя.EXE.Классы. Разделённость описания классов и реализации их методов соответствует оформлению их (классов) в виде модуля. Само описание класса, как правило, располагают в разделе Interface. В этом случае все его поля и методы будут доступны в других модулях и программах, а классы чаще всего и вводятся для того, чтобы их можно было использовать при написании различных программ. Описание реализации методов размещается в разделе Implementation, так как по самой сути классов требуется, чтобы конкретика работы их методов была скрыта от пользователей. Например, пользователь хочет в своей программе двигать фигуру Эллипс, и он знает, что класс tEllipse существует. Тогда пользователь просматривает все возможности данного класса, т.е. просматривает все его методы и свойства, и вызывает то, что ему нужно, а именно – метод движения фигуры (в наших примерах – метод Move). Как реализован вызываемый метод – пользователя не интересует (да это и скрыто от него). Если пользователь не находит нужный метод или найденный метод работает не так, как требуется, он может описать новый класс – наследник класса tEllipse и дополнить его всем необходимым для решения поставленной задачи. В разделе реализации можно использовать всё, что описано в разделе Interface. Директивы Private и Public. Эти директивы являются средством для управления областью видимости полей и методов. Private. (впервые появилась в Турбо Паскале 6.0) Ее роль в описании класса такая же, как и раздела implementation в модуле: скрыть некоторые детали описания класса. !!! Приватные (закрытые) поля и методы доступны только внутри того модуля, в котором описан класс. Структура описания класса с использованием директивы Private
Type Tobj = object
Например, interface { хотя класс и описан в общедоступном разделе interface доступ к полям x, y и методу Init получить невозможно. Получить значения x, y можно только, вызвав соответствующие функции GetX или GetY }Public. (впервые появилась в Турбо Паскале 7.0) Директива Public позволяет сначала указывать приватные поля и методы, а потом общедоступные. Иногда это удобно, чтобы не нарушать принятый порядок в описании класса, например:
§10. Некоторые рекомендации по созданию классов. В структуру класса желательно включать все действия (в виде методов), которые может проделывать объект данного класса, даже если какие-то действия не будут использоваться в данной программе. В процессе компиляции компилятор размещает в памяти коды только тех методов, вызов которых встречается в программе. Ни разу не вызванные методы компилятором игнорируются, так что “лишние” методы память не занимают. Обычно описание классов и методов оформляют в виде отдельных модулей. Покажем, как это делается на примере отлаженных модуля и программы, являющейся доработанной и дополненной версией программы из примера 2.6.
Пример 2.7. (в котором приведены коды Программы GRAFICA и Модуля FIGURA.) (Рекомендую набрать, запустить и разобрать работу данной программы) Программа GRAFICA, в которой реализовано движение графических фигур (сохранена в файле OOP_DO.pas ) ОТКРЫТЬ Модуль FIGURA, в котором реализована иерархия классов графических фигур (сохранен в файле FIGURA.pas ) ОТКРЫТЬ В заключении хочу заметить, что данную программу, может быть, было бы проще написать, используя привычный для Вас структурный подход. Все-таки эффект объектно-ориентированного подхода явно виден при разработке больших программных комплексов. Но нашей целью было не только теоретически изучить, но и практически применить основные принципы ООП. Для этого мы выбрали простую в реализации задачу. Для сравнения привожу текст программы, работающей аналогично программе из примера 2.7. , но написанной с использованием структурного подхода. Пример 2.8. (Рекомендую набрать, запустить и разобрать работу данной программы) Программа GRAF_STR, в которой реализовано движение графических фигур ОТКРЫТЬ Теперь, когда мы на примерах разобрали основные принципы ООП, рекомендую еще раз прочитать Главу 1 настоящего конспекта лекций.
|