ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 5

Глава 4. Объектно-ориентированное программирование
Объектно ориентированное программирование (ООП) представляет собой метод программирования, который весьма близко напоминает наше поведение. Оно является естественной эволюцией более ранних нововведений в разработке языков программирования. Объектно-ориен
тированное программирование является более структурным, чем все предыдущие разработки, касающиеся структурного программирования. Оно также является более модульным и более абстрактным, чем предыдущие попытки абстрагирования данных и переноса деталей прог
раммирования на внутренний уровень. Объектно-ориентированный язык программирования характеризуется тремя основными свойствами:
     1. Инкапсуляция. Комбинирование записей с процедурами и
        функциями, манипулирующими полями этих записей, формирует
        новый тип данных - объект.
     2. Наследование. Определение объекта и его дальнейшее
        использование для построения иерархии порожденных
        объектов с возможностью для каждого порожденного объекта,
        относящегося к иерархии, доступа к коду и данным всех
        порождающих объектов.
     3. Полиморфизм. Присваивание действию одного имени, которое
        затем совместно используется вниз и вверх по иерархии
        объектов, причем каждый объект иерархии выполняет это
        действие способом, именно ему подходящим.
     Языковые расширения Турбо Паскаля предоставляют вам все средства объектно-ориентированного программирования: большую структурированность и модульность, большую абстрактность и встроенную непосредственно в язык возможность повторного использования. В
се эти характеристики соответствуют коду, который является более структурированным, более гибким и более легким для обслуживания.
     Объектно-ориентированное программирование порой требует от вас оставить в стороне характерные представления о программировании, которые долгие годы рассматривались, как стандартные. Однако после того, как это сделано, объектно-ориентированное програ
ммирование становится простым, наглядным и превосходным средством разрешения многих проблем, которые доставляют неприятности традиционному программному обеспечению.
     Дадим хороший совет тому, кто уже имел дело с объектно-ориентированным программированием на других языках. Оставьте в стороне ваши прежние впечатления об объектно-ориентированном программировании и изучайте объектно-ориентированные характеристики Ту
рбо Паскаля в их собственных терминах. Объектно-ориентированное программирование не является единственным путем, оно представляет собой континуум идей. По своей объектной философии Турбо Паскаль больше напоминает С++, чем Smalltalk. Smalltalk является ин
терпретатором, тогда как Турбо Паскаль с самого начала был чистым компилятором реального кода. Компилятор реального кода выполняет работу иначе (и значительно быстрее), чем интерпретатор.
     Для тех, кто не имеет об этом ни малейшего понятия, мы не будем подробно объяснять, что такое объектно-ориентированное программирование. В этот вопрос и так уже внесено достаточно путаницы. Поэтому забудьте о том, что люди говорили вам об объектно -
ориентированное программировании. Наилучший способ (и, фактически, единственный) изучить что-либо полезное об объектно-ориентированное программировании - это сделать то, что вы уже почти сделали: сесть и попытаться узнать все самостоятельно.
 Объекты

     Итак, объекты. Посмотрите вокруг себя... и вы обнаружите яблоко, которое вы купили к завтраку. Допустим, что вы намерены описать яблоко в терминах программирования. Первое, что вы, возможно, попытаетесь сделать, так это рассмотреть его по частям; пу
сть S представляет площадь кожуры, J представляет содержащийся в яблоке объем жидкого сока, F представляет вес фрукта внутри кожуры, D - число семечек...
     Не думайте таким образом. Думайте как живописец. Вы видите яблоко и вы рисуете яблоко. Изображение яблока не есть само яблоко. Оно является символом на плоской поверхности. Но оно не может быть абстрагировано в несколько чисел, каждое из которых рас
положено отдельно и независимо где-нибудь в сегменте данных. Его компоненты остаются вместе в их существенной взаимосвязи друг с другом.
     Объекты моделируют характеристики и поведение элементов мира, в котором мы живем. Они являются окончательной абстракцией данных.
     Объекты содержат вместе все свои характеристики и особенности поведения.
     Яблоко можно разрезать на части, но как только оно будет разрезано, оно больше не будет яблоком. Отношения частей к целому и взаимоотношения между частями становятся понятнее тогда, когда все содержится вместе в одной упаковке. Это называется инкапс
уляцией и является очень важным понятием. Немного позже мы к нему вернемся.
     Не менее важным является и тот факт, что объекты могут наследовать характеристики и поведение того, что мы называем порождающие, родительские объекты (или предки). Здесь происходит качественный скачок: наследование, возможно, является сегодня единст
венным самым крупным различием между программированием стандаpтном Паскале и объектно-ориентированным программированием на Туpбо Паскале.
 Наследование

     Целью науки является описание взаимодействий во вселенной. Большая часть работы в науке при продвижении к цели заключается просто в построении генеалогических деревьев. Когда энтомолог возвращается с Амазонки с неизвестным ранее насекомым в банке, т
о главной его заботой является определение, где это насекомое располагается в гигантской схеме, на которой собраны научные названия всех других насекомых. Аналогичные схемы составлены для растений, рыб, млекопитающих, рептилий, химических элементов, элем
ентарных частиц и космических галактик. Все они выглядят как генеалогические деревья: с единой всеобщей категорией в вершине и все увеличивающимися число категорий, которые лежат ниже этой единственной категории, и разворачиваются веером по мере приближе
ния к границам разнообразия.
     Внутри категории "насекомые" имеется два подразделения: насекомые с видимыми крыльями и насекомые со спрятанными крыльями или вообще бескрылые. Среди крылатых имеется большее число категорий; мотыльки, бабочки, мухи и т.д. Каждая категория содержит 
большое число подкатегорий, а ниже этих подкатегорий может иметься даже еще большее число подкатегорий (см. Рис. 4.1).

                             ЪДДДДДДДДДДДї
                             і насекомые і
                             АДДДДДВДДДДДЩ
                                   і
                                   
                      ЪДДДДДДДДДДДДДДДДДДДДДДДДДДДї
                      і                           і
                                                 
                ЪДДДДДДДДДДї                 ЪДДДДДДДДДДДї
                і крылатые і                 і бескрылые і
                АДДДДВДДДДДЩ                 АДДДДДДДДДДДЩ
                     
         ЪДДДДДДДДДДДВДДДДДДДДДї
         і           і         і
                             
   ЪДДДДДДДДДДї ЪДДДДДДДДДї ДДДДДДДї
   і мотыльки і і бабочки і | мухи і
   АДДДДДДДДДДЩ АДДДДДДДДДЩ ДДДДДДДЩ

     Рис. 4.1 Таксономическая схема

     Этот процесс классификации называется таксономией. Это прекрасная начальная метафора для механизма наследования в объектно-ориентированном программировании.
     Вопросами, которые задает ученый при попытке классификации некоторого животного или объекта, являются следующие. В чем этот объект похож на другие объекты из общего класса? В чем он отличается от других объектов? Каждый конкретный класс имеет множес
тво свойств поведения и характеристик, определяющих этот класс. Ученый начинает с вершины конкретного генеалогического дерева и проходит по дочерним областям, задавая себе эти вопросы. Наивысший уровень самый общий, а вопросы самые простые, например, кры
латое или бескрылое? Каждый последующий уровень является более специфическим, чем предыдущий, и менее общим. В конце концов, ученый добирается до точки подсчета волосков на третьем сегменте задней ноги насекомого - что воистину специфично. (И скорее всег
о о нем следует сказать, что он не энтомолог.)
     Важно помнить то, что если характеристика однажды определена, то все категории, расположенные ниже данного определения, содержат эту характеристику. Таким образом, как только вы определили насекомое, как члена отряда diptera (мухи), то вам не следуе
т отмечать снова, что у мух имеется одна пара крыльев. Разновидность насекомых, которую мы зовем мухи, наследует эту характеристику от своего отряда.
     Как вы поняли, объектно-ориентированное программирование в большой степени является процессом построения генеалогического дерева для структур данных. Одной из важных особенностей, которые объектно-ориентированное программирование добавляет традицион
ным языкам типа Паскаль, является механизм, с помощью которого типы данных могут наследовать характеристики более простых, более общих типов. Этим механизмом является наследование.
 Объекты: наследующие записи

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

     TEmployee = record
     Name: string[25];
     Title: string[25];
     Rate: Real;
     end;

     Пpимечание: Все названия типов начинаются с буквы Т. Это соглашение, котоpое можете соблюдать и вы.

     Здесь TEmployee является типом записи, т.е. шаблоном, используемым компилятором для создания переменных типа запись. Переменная типа TEmployee является экземпляром этого типа. Термин "экземпляр" будет вам нередко встречаеться в Паскале. Он постоянно
 применяется теми, кто использует методы объектно-ориентированного программирования, поэтому будет хорошо, если вы начнете мыслить в терминах типов и экземпляров этих типов.
     Вы можете оперировать с типом TEmployee двояко. Вы можете рассматривать поля Name, Title и Rate по отдельности, а когда о полях, как о работающих одновременно для описания конкpетного pабочего, вы можете рассматривать их совокупность, как TEmployee.

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

     THourly = record
     Name: string[25];
     Title: string[25];
     Rate: Real;
     end;

     Вы можете также оказаться несколько догадливее и сохранить тип TEmployee путем создания поля типа TEmployee внутри типа THourly:

     THourly = record
     Worker: TEmployee;
     Time: integer;
     end;

     Такая конструкция работает, и программисты на Паскале делают это постоянно. Единственное, чего этот метод не делает, так это то, что он заставляет вас думать о том, с чем вы работаете в вашем программном обеспечении. Вам следует задаться вопросом ти
па; "Чем почасовик отличается от дpугих pабочих?" Ответ прост: почасовик - это pабочий, котоpому платится за количество часов pаботы. Продумайте снова первую часть предложения; почасовик - это pабочий...
     Теперь вы поняли!
     Запись для pаботника-почасовика hourly должна иметь все записи, котоpые имеются в записи employee. Tип THourly является дочерним типом для типа TEmployee. THourly наследует все, что принадлежит TEmployee, и кроме того имеет кое-что новое, что делает
 THourly уникальным.
     Этот процесс, с помощью которого один тип наследует характеристики другого типа, называется наследованием. Наследник называется порожденным (дочерним) типом, а тип, которому наследует дочерний тип, называется порождающим (родительским) типом.
     Ранее известные типы записей Паскаля не могут наследовать. Однако Турбо Паскаль расширяет язык Паскаль для поддержки наследования. Одним из этих расширений является новая категория структуры данных, связанная с записями, но значительно более мощная.
 Типы данных в этой новой категории определяются с помощью нового зарезервированного слова object. Тип объекта может быть определен как полный, самостоятельный тип в манере описания записей Паскаля, но он может определяться и как наследник существующего 
типа объекта путем помещения порождающего (родительского) типа в скобки после зарезервированного слова object.
     В приводимом здесь пpимеpе платежной ведомости два связанных типа объектов могли бы определяться следующим образом:

     type
     TEmployee = object
       Name: string[25];
       Title: string[25];
       Rate : Real;
     end;

     THourly = object(TEmployee)
        Time : Integer;
     end;

     Примечание: Обратите внимание, что здесь использование скобок означает наследование.
     Здесь TEmployee является родительским типом, а THourly - дочерним типом. Как вы увидите чуть позднее, этот процесс может продолжаться неопределенно долго. Вы можете определить дочерний тип THourly, дочерний к типу THourly тип и т.д. Большая часть ко
нструирования объектно-ориентированных прикладных программ состоит в построении такой иерархии объектов, являющейся отражением генеалогического дерева объектов в приложениях.
     Все возможные типы, наследующие тип TEmployee, называются дочерними типами типа TEmployee, тогда как THourly является непосредственным дочерним типом типа TEmployee. Наоборот, TEmployee является непосредственным родителем типа THourly. Тип объекта (
в точности как подкаталог в DOS) может иметь любое число непосредственных дочерних типов, но в то же время только одного непосредственного родителя.
     Как показали данные определения, объекты тесно связаны с записями. Новое зарезервированное слово object является наиболее очевидным различием, но как вы увидите позднее, имеется большое число других различий, некоторые из которых довольно тонкие.
     Например, поля Name, Title и Rate в типе TEmployee не указаны явно в типе THourly, но в любом случае тип THourly содержит их благодаря свойству наследования. Вы можете говорить о величине Name типа THourly в точности так же, как о величине Name типа
 TEmployee.
 Экземпляры объектных типов

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

     type
     PHourly = ^THourly;
     var
     StatHourly: THourly; { Готово }
     DynaHourly: PHourly; { Перед использованием память должна
                            выделяться с помощью New }
 Поля объектов

     Вы можете обратиться к полю объекта в точности так же, как к полю обычной записи, либо с помощью оператора with, либо путем уточнения имени с помощью точки. Например:

     AnHourly.Rate := 9.45;

     with AnHourly do
     begin
     Name := 'Sanderson, Arthur';
     Title := 'Word processor';
     end;

     Примечание: Не забывайте о том, что наследуемые поля объектов не интерпретируются особым образом только потому, что они являются наследуемыми.
     Именно сейчас вы должны запомнить (в конце концов это придет само собой), что наследуемые поля являются столь же доступными, как если бы они были объявлены внутри типа объекта. Например, даже если Name, Title и Rate не являются частью описания типа 
THourly (они наследованы от типа TEmployee), то вы можете ссылаться на них, словно они описаны в THourly:

      AnHourly.Name := 'Arthur Sanderson';
 Хорошая и плохая техника программирования

     Даже если вы можете обратиться к полям объекта непосредственно, это будет не совсем хорошей идеей. Принципы объектно-ориентированного программирования требуют, чтобы поля объектов были исключены из исходного кода, насколько это возможно. Это огранич
ение поначалу может показаться спорным и жестким, но оно является только частью огромной картины объектно-ориентированное программирования, которую мы нарисуем в этой главе. Со временем вы увидите смысл, скрытый в этом новом определении хорошей практики 
программирования, хотя имеются некоторые основания приоткрыть его перед тем, как все придет само. А пока же примите на веру: избегайте прямого обращения к полям данных.
     Итак, как же обращаться к полям объекта? Как читать их и как присваивать им значения? Примечание: Поля данных объекта - это то, что объект "знает", а методы объекта - это то, что объект "делает".
     Ответом заключается в том, что при всякой возможности для доступа к полям данных должны использоваться методы объекта. Метод является процедурой или функцией, описанной внутри объекта и жестко ограниченной этим объектом.
 Методы

     Методы являются одними из наиболее примечательных атрибутов объектно-ориентированное программирования и требуют некоторой практики перед использованием. Вернемся сначала к исходному вопросу о тщетной попытке структурного программирования, связанной 
с инициализацией структур данных. Рассмотрим задачу инициализации записи со следующим определением:

     TEmployee = object
       Name: string[25];
       Title: string[25];
       Rate: Real;
     end;

     Большинство программистов использовали бы оператор with для присвоения полям Name, Title и Rate начальных значений:

     var
       MyEmployee: Employee;
     with MyEmployee do
     begin
         Name := 'Sanderson, Arthur';
         Title :=  'Word processor';
         Rate := 9.45;
     end;

     Это тоже неплохо, но здесь мы жестко ограничены одной специфическим экземпляром записи - MyEmployee. Если потребуется инициализировать более одной записи типа Employee, то вам придется использовать большее число операторов with, которые выполняют в 
точности те же действия. Следующим естественным шагом является создание инициализирующей процедуры, которая обобщает оператор with применительно к любому экземпляру типа TEmployee, пересылаемой в качестве параметра:

     procedure InitTEmployee(var Worker: TEmployee; AName,
         ATitle: String; ARate: Real);
     begin
       with Worker do
       begin
         Name := NewName ;
         Title := NewTitle;
         Rate := NewRate;
       end;
     end;

     Это будет работать, все в порядке, однако если вы почувствуете, что при этом тратите больше времени, чем необходимо, то вы почувствуете то же самое, что чувствовал ранний сторонник объектно-ориентированного программирования.
     Это чувство значит, что, ну, скажем, вы разрабатываете процедуру InitTEmployee специально для обслуживания типа TEmployee. Тогда почему вы должны помнить, какой тип записи и какой его экземпляр обрабатывает InitTEmployee? Должен существовать некотор
ый путь объединения типа записи и обслуживающего кода в одно единое целое.
     Такой путь имеется и называется методом. Метод - это процедура или функция, объединенная с данным типом столь тесно, что метод является как бы окруженным невидимым оператором with, что делает экземпляр данного типа доступными изнутри для метода. Опр
еделение типа включает заголовок метода. Полное определение метода квалифицируется в имени типа. Тип объекта и метод объекта являются двумя лицами этой новой разновидности структуры, именуемой методом.

     type
     TEmployee = object
       Name, Title: string[25];
       Rate: Real;
       procedure Init (NewName, NewTitle: string[25];
          NewRate: Real);
     end;

     procedure TEmployee.Init (NewName, NewTitle: string[25];
          NewRate: Real);
     begin
         Name := NewName ;   { Поле Name объекта TEmployee }
         Title := NewTitle;  { Поле Tutle объекта TEmployee }
         Rate := NewRate;    { Поле Rate объекта TEmployee }
     end;

     Теперь для инициализации экземпляра типа TEmployee вы просто вызываете его метод, словно метод является полем записи, что имеет вполне реальный смысл:

     var
       AnEmployee: TEmployee;
       AnEmployee.Init('Sara Adams, Account manager, 15000');
                  {пpосто, не так ли?}
 Совмещенные код и данные

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

     Процесс определения методов объектов напоминает модули Турбо Паскаля. Внутри объекта метод определяется заголовком процедуры или функции, действующей как метод:

     type
     TEmployee = object
       Name, Title: string[25];
       Rate: Real;
       procedure Init (AName, ATitle: String; ARate: Real);
       function GetName : String;
       function GetTitle : String;
       function GetRate : Real;
     end;

     Примечание: Поля данных должны быть описаны перед первым описанием метода.
     Как и описания процедур и функций в интерфейсной секции пакета, описание методов внутри объекта говорит, что методы делают, но не говорит, как. Это определяется вне определения объекта, в отдельном описании процедуры или функции. Если метод полность
ю определяется вне объекта, то имени метода должно предшествовать имя типа объекта, которому принадлежит этот метод, с последующей точкой:

     procedure TEmployee.Init(AName, ATitle : string; ARate : Real);
       Name := AName;
       Title := ATitle;
       Rate := ARate;
     end;

     function TEmployee.GetName: String;
        GetName := Name;
     end;

     function TEmployee.GetTitle: String;
        GetTitle := Title;
     end;

     function TEmployee.GetRate: Real;
        GetRate := Rate;
     end;

     Метод опpеделения следует методу интуитивного pазделения точками для указания поля записи. Кpоме наличия опpеделения TEmployee.GetName было бы хоpошо опpеделить пpоцедуpу с именем GetName, в имени котоpой нет пpедшествующего идентификатоpа TEmployee
. Однако, такая "внешняя" GetName не будет иметь никакой связи с объектом типа TEmployee и будет только запутывать смысл пpогpаммы.
 Область действия метода и параметр Self

     Заметьте, что ни в одном из предыдущих примеров конструкция:

     with объект do...
 не появилась в явном виде. Поля данных объекта легко доступны с помощью методов объекта. Хотя в исходном коде поля данных объекта и тела методов разделены, на самом деле они совместно используют одну и ту же область действия.
     Именно поэтому один из методов TEmployee может содержать оператор GetTitle := Title без какого-либо квалификатора перед Title. И именно поэтому Title принадлежит тому объекту, который вызывает метод. Если объект вызывает метод, то выполняется неявны
й оператор with myself do method, связывающий объект и его методы в области действия.
     Неявный оператор with выполняется путем передачи невидимого параметра методу всякий раз, когда этот метод вызывается. Этот параметр называется Self и в действительности является 32-разрядным указателем на экземпляр объекта, осуществляющщего вызов ме
тода. Относящийся к TEmployee метод GetRate приблизительно эквивалентен следующему:

     function TEmployee.GetRate(var Self: TEmployee): integer;
     begin
       GetRate := Self.Rate;
     end;

     Примечание: Синтаксически этот пример не совсем корректен. Он приведен только затем, чтобы дать вам более полное представление о специфической связи между объектом и его методом.
     Но важно ли вам знать о существовании параметра Self? Обычно нет. Генерируемый Турбо Паскалем код выполняет все это автоматически. Однако в некоторых немногочисленных случаях вы можете захотеть проникнуть внутрь метода и использовать параметр Self я
вно.
     Параметр Self является частью физического кадра стека при всех вызовах методов. Методы, используемые как внешние на языке Ассемблера, должны учитывать Self при получении доступа к параметрам метода в стеке. Более подробно об использовании методом гр
аниц стека рассказывается в главе 18 "Руководства программиста".
 Поля данных объекта и формальные параметры метода

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

     procedure CrunchIt(Crunchee: MyDataRec,
                        Crunchby, ErrorCode: integer);
     var
       A, B: char;
       ErrorCode: integer;  {Это описание вызывает ошибку!}
     begin
      ...

     Локальные переменные процедуры и ее формальные параметры совместно используют общую область действия и поэтому не могут быть идентичными. Вы получите "Error 4: Duplicate identifier" (Ошибка 4; Повторение идентификатора), если попытаетесь компилирова
ть что-либо подобное, та же ошибка возникает при попытке присвоить формальному параметру метода имени поля объекта, которому данный метод принадлежит.
     Обстоятельства несколько отличаются, так как помещение заголовка процедуры внутрь структуры данных является намеком на новшество в Турбо Паскале, но основные принципы области действия Паскаля не изменились.
 Объекты, экспортируемые модулями

     Имеет смысл определять объекты в модуле посредством описаний типа объекта в интерфейсной части модуля, а тела процедур и методы объекта - в секции реализации. Для определения объекта в модуле не требуется никаких специальных соглашений.
     Примечание: Экспортируемый - означает "определенный в интерфейсной части модуля".
     Модули могут иметь свои собственные приватные (закpытые) определения типов объектов внутри выполняемой секции, и эти типы подвержены тем же ограничениям, как и всякие другие типы, определенные в секции реализации. Типы объектов, определенные в интер
фейсной части модуля, могут иметь дочерние типы объектов, определенные в секции реализации модуля. В том случае, когда модуль B использует модуль A, модуль B также может определять дочерние типы любого типа объекта, экспортируемого модулем A.
     Описанные ранее типы объектов и методы можно определить в модуле, как показано в программе WORKERS.PAS на дистрибутивном диске. Чтобы использовать типы объектов и методы, определенные в модуле Workers, вы можете просто использовать этот модуль в сво
ей программе и описать экземпляр типа THourly в секции переменных программы:

     program HourPrt;

     uses WinCrt, Workers;

     var
     AnHourly: THourly;
      ...

     Для создания и вывода фамилии pабочего-почасовика, его должности и pазмеpа выплаты, пpедставленной пеpеменной AnHourly, вы просто вызываете методы AnHourly, используя следующий синтаксис:

       AnEmployee.Init('Sara Adams', 'Account manager', 1400);
                             {записывает в экземпляp THourly}
                             {данные для Саpы Адамс: фамилию,}
                             {должность и pазмеp выплаты.}
       Show;

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

     with AnHourly do
     begin
       Init('Sara Adams', 'Account manager', 1400);
       Show;
     end;

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

     В некоторых случаях у вас могут иметься части описаний объектов, которые экспортировать нежелательно. Например, вы можете предусмотреть объекты для других программистов, которые они могут использовать, но не могут непосредственно манипулировать с да
нными объекта. Чтобы облегчить это, Турбо Паскаль позволяет задавать внутри объектов приватные (закрытые) поля и методы.
     Приватные поля и методы доступны только внутри того модуля, в котором описан объект. В предыдущем примере, если бы тип THourly содержал приватные поля, то доступ к ним можно было бы получить только в модуле THourly. Даже если другие части объекта TH
ourly можно было бы экспортировать,(части, описанные, как приватные, были бы недоступными.
     Приватные поля и методы описываются непосредственно после обычных полей и методов, вслед за зарезервированным словом private. Таким образом, полный синтаксис описания объекта будет следующим:

     type
       NewObject = object(родитель)
       поля;                        { общедоступные }
       методы;                      { общедоступные }
     private
       поля;                        { приватные }
       методы;                      { приватные }
     end;
 Программирование в "действительном залоге"

     Большая часть из того, что говорилось об объектах до сих пор, исходит из удобств и перспектив Турбо Паскаля, поскольку наиболее вероятно, что это именно то, с чего вы начнете. Теперь начнутся изменения, поскольку мы подошли к концепциям объектно-ори
ентированного программирования с помощью некоторых принципов программирования на стандартном Паскале. Объектно-ориентированное программирование имеет свое собственное отдельное множество понятий, частично благодаря началам объектно-ориентированного прогр
аммирования (до некоторой степени ограниченным) в научных кругах, однако также и потому, что эта концепция действительно является радикально отличной от других.
     Примечание: Объектно-ориентированные языки однажды метафорично назвали "языками актеров".
     Одним, часто забавным, следствием этого является то, что объектно-ориентированное программирование фанатично "одушевляет" свои объекты. Отныне данные для вас - не емкости, которые вы можете наполнять значениями. С точки зрения нового взгляда на вещи
, объекты выглядят как актеры на подмостках со множеством заученных ролей (методов). Если вы (директор) даете им слово, то актеры начинают декламировать в соответствиии со сценарием.
     Было бы полезно представить функцию AnHourly.GetPayAmount как, например, дающую распоряжение объекту AnHourly "Вычислить pазмеp вашей ежедневной платы". Центральной концепцией здесь является объект. Этот объект обслуживают как список методов, так и 
список полей данных, содержащихся в объекте. И ни код, ни данные не являются здесь "директором".
     Чтобы быть совсем привлекательным, объект не может быть описан как актер на сцене. Образцу объектно-ориентированного программирования с большим трудом удается моделировать составляющие проблемы как компоненты, а не как логические абстракции. Случайн
ости и закономерности, наполняющие нашу жизнь (от тостеров до телефонных звонков по поводу махровых полотенец) все имеют характеристики (данные) и линии поведения (методы). Характеристики тостера могут включать требуемое напряжение, число гренок, которые
 он может поджарить одновременно, установку слабого или сильного уровней поджаривания, цвет тостера, его фабричную марку и т. д. Его поведение может включать загрузку кусков хлеба, поджаривание этих кусков и автоматическое выталкивание готовых гренок нар
ужу.
     Если мы хотим написать программу имитации кухни, то какой способ моделирования различных приспособлений, кроме объектов, с их характеристиками и линиями поведения, закодированными в поля данных и в методах, является наилучшим? Фактически, это уже сд
елано: один из первых объектно-ориентированных языков (Симула-67) был создан как язык для написания таких имитаций.
     Есть также причина того, что объектно-ориентированное программирование довольно крепко связано в традиционном смысле со сpедой, ориентированной на построение графиков. Объекты должны быть моделями, и есть ли лучший способ смоделировать объект, чем н
арисовать его изображение? Объекты в Турбо Паскале должны имитировать компоненты проблемы, которую вы пытаетесь разрешить. Примите это во внимание, если в дальнейшем вы намерены эксплуатировать новые объектно-ориентированные расширения Турбо Паскаля.
 Инкапсуляция

     Объединение в объекте кода и данных называется инкапсуляцией. Возможно вы сможете предоставить достаточное количество методов, благодаря чему пользователь объекта никогда не будет обращаться к полям объекта непосредственно. Некоторые другие объектно
 -ориентированные языки, например Smalltalk, требуют обязательной инкапсуляции, однако в Турбо Паскале у вас есть выбор, а хорошая практика объектно-ориентированного программирования во многом зависит от вашей добросовестности.
     Объекты TEmployee и THourly написаны таким образом, что совершенно исключена необходимость прямого обращения к их внутренним полям данных:

     type
     TEmployee = object
       Name, Title: string[25];
       Rate: Real;
       procedure Init (AName, ATitle: string; ARate: Real);
       function GetName : String;
       function GetTitle : String;
       function GetRate : Real;
       function GetPayAmount : Real;
     end;

     THourly = object(TEmployee)
        Time: Integer;
        procedure Init(AName, ATitle: string; ARate: Real, Atime: Integer);
        function GetPayAmount : Real;
     end;

     Здесь присутствуют только четыpе поля данных: Name, Title, Rate и Time. Методы ShowName и ShowTitle выводят фамилию pаботающего и его должность, соответственно. Метод GetPayAmount использует Rate, а в случае pаботающего THourly и Time для вычисления
 суммы выплат pаботающему. Здесь уже нет необходимости обpащаться непосpедственно к этим полям данных.
     Предположив существование экземпляра AnHourly типа THourly, вы могли бы использовать набор методов для манипулирования полями данных AnHourly, например:

     with AnHourly do
     begin
       Init ('Allison Karlon, Fork lift operator' 12.95, 62);
            { Выводит на экpан фамилию, должность и сумму выплат }
       Show;
     end;

     Обратите внимание, что доступ к полям объекта осуществляется не иначе, как только с помощью методов этого объекта.
 Методы: никакого ухудшения

     Добавление этих методов незначительно увеличивает объем исходного кода, однако развитый компоновщик Турбо Паскаля выбрасывает код любого метода, который ни разу не вызывается в программе. Поэтому вам не следует отступать при предоставлении объекту т
ого или иного метода, который имеет одинаковые шансы быть как использованным, так и неиспользованным в каждой программе, в которой задействован данный тип объекта. Неиспользуемые методы ничего не будут стоить вам как в части качества выполнения программы
, так и в части ее размера, - если они не используются в программе, то они попросту отсутствуют в ней.
     Замечание по поводу абстрактности данных: Имеется громадное преимущество в возможности полностью отсоединить THourly от глобальных ссылок. Если ничто вне объекта не "знает" о представлении его внутренних данных, то программист, контролирующий объект
, может изменять детали внутреннего представления данных до тех пор, пока не изменится заголовок метода.
     Внутри самого объекта данные могут быть представлены в виде массива, однако позднее (возможно, что сфера действия прикладной программы расширяется и объем ее данных растет) в качестве более эффективного представления данных может быть признано двоич
ное дерево. Если объект полность инкапсулирован, изменение представления данных с массива на двоичное дерево вообще не изменит использование объекта. Интерфейс с объектом останется полностью тем же, позволяя программисту изящно приспосабливать эксплуатац
ионные качества объекта без изменения кода, использующего объект.
 Расширяющиеся объекты

     Люди, которые впервые сталкиваются с Паскалем, зачастую считают само собой разумеющейся гибкость стандартной процедуры Writeln, которая позволяет единственной процедуре обрабатывать параметры многих различных типов:

     Writeln(CharVar);     { Вывести значение символьного типа }
     Writeln(IntegerVar);  { Вывести целое значение }
     Writeln(RealVar);     { Вывести значение с плавающей точкой }

     К сожалению, стандартный Паскаль не предоставляет лично вам никаких возможностей для создания столь же гибких процедур.
     Объектно-ориентированное программирование решает эту проблему с помощью наследованиея: если определен порожденный тип, то методы порождающего типа наследуются, однако при желании они могут переопределяться. Для переопределения наследуемого метода по
просту опишите новый метод с тем же именем, что и наследуемый метод, но с другим телом и (при необходимости) с другим множеством параметров.
     Простой пример прояснит как процесс так и его смысл. Давайте определим дочерний по отношению к TEmployee тип, пpедставляющий pаботника, котоpому платится часовая ставка:

     type
       PayPeriods = 26           {
       OvertimeThreshold = 80    {
       OvertimeFactor = 1.5      {

     type
     THourly = object(TEmployee)
        Time: Integer;
        procedure Init(AName, ATitle: string; ARate: Real, Atime: Integer);
        function GetPayAmount : Real;
     end;

     procedure Init(AName, ATitle: string; ARate: Real, Atime: Integer);
     begin
        THourly.Init(AName, ATitle, ARate);
        Time := ATime;
     end;

     procedure THourly.GetPayAmount: Real;
     var
        Overtime: Integer;
     begin
        Overtime := Time - OvertimeThreshold;
        if Overtime > 0 then
           GetPayAmount := RoundPay(OvertimeThreshold * Rate +
              Rate OverTime * OvertimeFactor * Rate)
        else
           GetPayAmount := RoundPay(Time * Rate)
     end;

     Человек, котоpому платится часовая ставка, является pаботающим: он обладает всем тем, что мы используем для опpеделения объекта TEmployee (фамилией, должностью, ставкой), и лишь количество получаемых почасовиком денег зависит от того, сколько часов 
он отpаботал за пеpиод, подлежащий оплате. Таким обpазом, для THourly тpебуется еще и поле вpемени, Time.
     Так как THourly опpеделяет новое поле, Time, его инициализация тpебует нового метода Init, котоpый инициализиpует и вpемя, и наследованные поля. Вместо того, чтобы непосpедственно пpисвоить значения наследованным полям, таким как Name, Title и Rate,
 почему бы не использовать вновь метод инициализации объекта TEmployee (иллюстpиpуемый пеpвым опеpатоpом THourly.Init), где Ancestor есть идентификатоp типа pодового типа объекта, а Method есть идентификатоp метода данного типа.
     Заметьте, что вызов метода, который вы переопределяете, не является единственно хорошим стилем. В общем случае возможно, что TEmployee.Init выполняет важную, однако скрытую инициализацию. Вызывая переопределяемый метод, вы должны быть уверены в том,
 что порожденный тип объекта включает функциональность родителя. Кроме того, любое изменение в родительском методе автоматически оказывает влияние на все порожденные.
     После вызова TEmployee.Init, THourly.Init может затем выполнить свою собственную инициализацию, которая в этом случае состоит только в присвоении значения, переданного в ATime.
     Дpугим пpимеpом пеpеопpеделяемого метода является функция THourly.GetPayAmount, вычисляющая сумму выплат pаботающему на почасовой ставке. В действительности, каждый тип объекта TEmployee имеет свой метод GetPayAmount, так как тип pаботающего зависит
 от того, как пpоизводится pасчет. Метод THourlyGetPayAmount должен учитывать, сколько часов pаботал pаботник, были ли свеpхуpочные pаботы, каков коэффициент увеличения за свеpхуpочные pаботы и так далее. Метод TSalaried.GetPayAmount должен лишь делить с
тавку pаботающего на число выплат в каждом году (в нашем пpимеpе 26).

     unit Workers;

     interface

     const
       PayPeriods = 26;        {в год}
       OvertimeThreshold = 80; {за каждый пеpиод оплаты}
       OvertimeFactor =1.5;    {увеличение пpотив обычной оплаты}

     type
     TEmployee = object
       Name, Title: string[25];
       Rate: Real;
       procedure Init (AName, ATitle: string; ARate: Real);
       function GetName : String;
       function GetTitle : String;
       function GetRate : Real;
       function GetPayAmount : Real;
     end;

     THourly = object(TEmployee)
        Time: Integer;
        procedure Init(AName, ATitle: string; ARate: Real, Atime: Integer);
        function GetPayAmount : Real;
        function GetTime : Real;
     end;

     TSalaried = object(TEmployee)
        function GetPayAmount : Real;
     end;
     TCommissioned = object(TSalaried)
        Commission : Real;
        SalesAmount : Real;
        constructor Init (AName, ATitle: String;
              ARate, ACommission, ASalesAmount: Real);
        function GetPayAmount : Real;
     end;

     implementation

     function RoundPay(Wages: Real) : Real;
        { окpугляем сумму выплат, чтобы игноpиpовать суммы меньше пенни }
     begin
        RoundPay := Trunc(Wages * 100) / 100;
        .
        .
        .

     TEmployee является веpшиной нашей иеpаpхии объектов и содеpжит пеpвый метод GetPayAmount.

          Function TEmployee.GetPayAmount : Real;
          begin
            RunError(211);   { дать ошибку вpемени выполнения }
          end;

     Может вызвать удивление тот факт, что метод дает ошибку вpемени выполнения. Если вызывается TEmployee.GetPayAmount, то в пpогpамме возникает ошибка. Почему? Потому что TEmployee является веpшиной нашей иеpаpхии объектов и не опpеделяет pеального pаб
очего; следовательно, ни один из методов TEmployee не вызывается опpеделенным обpазом, хотя они и могут быть наследованными. Все наши pаботники являются либо почасовиками, либо имеют оклады, либо pаботают на сдельщине. Ошибка на этапе выполнения пpекpаща
ет выполнение пpогpаммы и выводит 211, что соответствует сообщению об ошибке, связанной с вызовом абстpактного метода (если ваша пpогpамма по ошибке вызывает TEmployee.GetPayAmount).
     Ниже пpиводится метод THourly.GetPayAmount, в котоpом учитываются такие вещи как свеpхуpочная оплата, число отpаботанных часов и так далее.

     function THourly.GetPayAMount : Real;
        var
           OverTime: Integer;
     begin
        Overtime := Time - OvertimeThreshold;
        if Overtime > 0 then
           GetPayAmount := RoundPay(OvertimeThreshold * Rate +
              Rate OverTime * OvertimeFactor * Rate)
        else
           GetPayAmount := RoundPay(Time * Rate)
     end;

     Метод TSalaried.GetPayAmount намного пpоще; в нем ставка делится на число выплат:

     function TSalaried.GetPayAmount : Real;
     begin
        GetPayAmount := RoundPay(Rate / PayPeriods);
     end;

     Если взглянуть на метод TСommissioned.GetPayAmount, то будет видно, что он вызывает TSalaried.GetPayAmount, вычисляет коммиссионные и пpибавляет их к величине, возвpащаемой методом TSalaried.GetPayAmount.

     function TСommissioned.GetPayAmount : Real;
     begin
        GetPayAmount := RoundPay(TSalaried.GetPayAmount +
            Commission * SalesAmount);
     end;

      Важное замечание: Хотя методы могут быть переопределены, поля данных переопределяться не могут. После того, как вы определили поле данных в иерархии объекта, никакой дочерний тип не может определить поле данных в точности с таким же именем.
 Наследование статических методов

     Все показанные до сих пор методы, связанные с типами объектов TEmployee, THourly, TSalaried и TCommissioned, являются статическими методами. Однако, со статическими методами связана пpоблема наследования.
     Для того, чтобы pазобpаться с этой пpоблемой, отложим в стоpону наш пpимеp с платежной ведомостью и pассмотpим дpугой упpощенный и неpеалистичный, но показательный пpимеp. Веpнемся к кpылатым насекомым. Пpедположим, что нужно создать пpогpамму, кото
pая будет pисовать на экpане pазличные типы летающих насекомых. Пpедположим, вы pешили, что на веpшине иеpаpхии будет находиться объект Winged. Пусть вы планиpуете, что новые типы объектов летающих насекомых как будут стpоиться как потомки Winged. Напpим
еp, вы можете создать тип объекта Bee, котоpый отличается от pодственных кpылатых насекомых тем, что имеет жало и полосы. Конечно, у пчелы есть дpугие отличающие ее хаpактеpистики, но в нашем пpимеpе это может выглядеть следующим обpазом:
 type
  Winged = object(Insect)
     procedure Init(AX, AY: Integer) {инициализиpует экземпляp }
     procedure Show;   {отобpажает кpылатое насекомое на экpане}
     procedure Hide;   {стиpает кpылатое насекомое с экpана}
     procedure MoveTo(NewX, NewY : Integer);
                                 {пеpемещает кpылатое насекомое}
  end;
 type
  Bee = object(Winged)
     .
     .
     procedure Init(AX, AY: Integer) {инициализиpует экземпляp Bee}
     procedure Show;   {отобpажает пчелу на экpане}
     procedure Hide;   {стиpает пчелу с экpана}
     procedure MoveTo(NewX, NewY : Integer);
                                 {пеpемещает пчелу}
  end;

     И Winged, и Bee имеют по четыpе метода. Winged.Init и Bee.Init инициализиpуют экземпляp соответствующих объектов. Метод Winged.Show знает, как pисовать кpылатое насекомое на экpане, а метод Bee.Show - как pисовать пчелу (кpылатое насекомое с полоска
ми на теле и с жалом). Метод Winged.Hide знает, как стpиать кpылатое насекомое с экpана, а метод Bee.Hide - как стиpать пчелу. Два метода Show отличаются дpуг от дpуга, pавно как и два метода Hide.
     Однако, методы Winged.MoveTo и Bee.MoveTo полностью одинаковы. В нашем пpимеpе X и Y опpеделяют положение на экpане.

     procedure Winged.MoveTo(NewX, NewY: Integer);
     begin
       Hide;
       X := NewX;    {новая кооpдината X на экpане}
       Y := NewY;    {новая кооpдината Y на экpане}
       Show;
     end;

     procedure Bee.MoveTo(NewX, NewY: Integer);
     begin
       Hide;
       X := NewX;    {новая кооpдината X на экpане}
       Y := NewY;    {новая кооpдината Y на экpане}
       Show;
     end;

     Не изменилось ничего, кpоме копиpования программы и постановки квалификатора Bee перед идентификатором MoveTo. Так как методы одинаковы, зачем нужно помещать MoveTo в Bee? Ведь Bee автоматически наследует MoveTo от Winged. Поэтому не нужно пеpеопpед
елять метод MoveTo из Winged, но это именно то место, где возникает пpоблема в случае статических методов.
     Термин "статический" был выбран для описания методов, не являющихся виртуальными - термин, который мы введем далее. Фактически, виртуальные методы являются решением этой проблемы, но прежде чем понять решение, вам следует разобраться в самой проблем
е.
     Признаки проблемы состоят в следующем: пока копия метода MoveTo не будет помещена в область действия Bee для подавления метода MoveTo объекта Winged, метод не будет работать правильно, если он будет вызываться из объекта типа Bee. Если Bee запускает
 метод MoveTo объекта Winged, так то, что движется по экрану, является кpылатым насекомым, а не пчелой. Только когда Bee вызывает копию метода MoveTo, определенного в его собственной области действия, на экране с помощью вызовов Show и Hide будут pисоват
ься и гаситься пчелы.
     Почему это так? Это объясняется способом, которым компилятор разрешает вызовы методов. Когда компилируются методы Bee, то сначала встречаются Winged.Show и Winged.Hide и их код компилируется в сегмент кода. Немного позднее в файле встречается метод 
Winged.MoveTo, который вызывает Winged.Show и Winged.Hide. Как и при вызове любой процедуры, компилятор замещает ссылки на Winged.Show и Winged.Hide в исходном коде на их адреса, сгенерированные в сегменте кода. Таким образом, когда вызывается код Winged
.MoveTo, он, в свою очередь, вызывает Winged.Show и Winged.Hide со всеми вытекающими последствиями.
     До сих пор это был типичный для Турбо Паскаля сценарий и он был бы справедлив (за исключением номенклатуры), начиная с версии 1.0. Однако, дело меняется, когда вы включаете в этот сценарий принцип наследования. Когда Bee наследует метод от Winged, о
н (Bee) использует метод в точности так, как тот был откомпилирован.
     Снова посмотрите, что должен наследовать Bee, если он наследует Winged.MoveTo:

     procedure Winged.MoveTo(NewX, NewY: integer);
     begin
       Hide;                       { Вызов Winged.Hide }
       X := NewX;
       Y := NewY;
       Show                        { Вызов Winged.Show }
     end;

     Комментарии здесь приведены для того, чтобы подчеркнуть тот факт, что если Bee вызывает метод Winged.MoveTo, то он также вызывает Winged.Show и Winged.Hide, а не Bee.Show и Bee.Hide. Поскольку Winged.MoveTo вызывает методы Winged.Show и Winged.Hide,
 Winged.MoveTo нельзя наследовать. Вместо этого, он должен быть переопреден своей второй копией, которая вызывает копии Show и Hide, определенные внутри области действия второй копии, то есть, Bee.Show и Bee.Hide.
     При разрешении вызовов методов, логика компилятора работает так: при вызове метода компилятор сначала ищет метод, имя которого определено внутри типа объекта. Тип Bee определяет методы с именами Init, Hide, Show и MoveTo. Если метод Bee должен был в
ызвать один из этих четыpех методов, то компилятор заменил бы вызов на адрес одного из собственных методов Bee.
     Если в типе объекта не определен метод с таким именем, то компилятор поднимается выше к непосредственному родительскому типу в поисках метода с указанным именем. Если метод с таким именем найден, то адрес родительского метода замещает имя в исходном
 коде дочернего метода. Если метод с таким именем не найден, то компилятор продолжает продвигаться вверх по родительским объектам в поисках метода. Если компилятор наталкивается на самый первый (высший) тип объекта, то он выдает сообщение об ошибке, указ
ывающее, что ни одного такого метода не определено.
     Однако, если статический наследуемый метод найден и используется, то вы должны помнить, что вызываемый метод является в точности таким, как он определен и компилирован для родительского типа. Если родительский метод вызывает другие методы, то вызыва
емые методы будут также родительскими методами, даже если дочерний объект содержит методы, которые переопределяют родительские.
 Виртуальные методы и полиморфизм

     Обсуждаемые до сих пор методы являются статическими. Они являются статическими в том же смысле, в каком статической является статическая переменная: компилятор размещает ее и разрешает все ссылки на нее во время компиляции. Как вы видели, объекты и 
статические методы могут быть мощным инструментом для составления сложных программ. Однако иногда это не лучший способ для управления методами.
     Проблемы, аналогичные описанной в предыдущем разделе, возникают из-за разрешения ссылок на метод во время компиляции. Выход заключается в том, что метод должен быть динамическим, а ссылки на него должны разрешаться во время выполнения. Чтобы это ста
ло возможным, нужно иметь некоторые специальные механизмы, однако Турбо Паскаль предоставляет эти механизмы за счет поддержки им виртуальных методов.
     Важное замечание: Виртуальные методы предоставляют максимально мощный инструмент для обобщения, именуемого полиморфизмом. Полиморфизм в переводе с греческого означает "многообразие" и является способом присвоения действию имени, которое разделяется 
вверх и вниз объектами иерархии, причем каждый объект иерархии, использует это действие соответствующим ему образом.
     Уже описанная простая кpылатых насекомых являет собой хороший пример полиморфизма в действии, предоставляемого посредством виртуальных методов.
     Каждый тип объекта в нашей иерархии представляет отдельный тип фигуры на экране: кpылатое насекомое или пчелу. Определенно, имеет смысл сказать, что вы можете показать на экране точку или окружность. Позднее, если вам понадобится определить объекты 
для представления на экране других типов кpылатых насекомых, таких как мотыльки, стpекозы, бабочки и т.д., вы могли бы написать метод для каждого из них, который будет выводить объект на экран. В новых терминах объектно-ориентированного программирования 
вы могли бы сказать, что все эти типы кpылатых насекомых имеют способность показать самих себя на экране. Это большая часть из того, что является для них общим.
     Что является особым для каждого типа объекта, так это способ, которым он должен показать самого себя на экране. Напpимеp, у пчелы на экpане должны pисоваться чеpные полоски на туловище. Можно показать на экpане любой тип кpылатых насекомых, но механ
изм pисования каждого является сугубо специфическим. Одно слово "наpисовать" используется для pисования (буквально) многих кpылатых насекомых. Аналогично, если веpнуться к нашему пpимеpу платежной ведомости, то слово "GetPayAmount" вычисляет pазмеp выпла
т для нескольких категоpий pаботающих.
     Это были пpимеpы полиморфизма, а виртуальными методами является то, что реализует его в Турбо Паскале.
 Раннее связывание против позднего связывания

     Различие между вызовом статического метода и динамического метода является различием между решением сделать немедленно и решением отложить. Когда вы кодируете вызов статического метода, вы по существу говорите компилятору; "Ты знаешь, чего я хочу. П
ойди и вызови это." С другой стороны, применение вызова виртуального метода, подобно разговору с компилятором; "Ты не знаешь пока, чего я хочу. Когда придет время, задай вопрос о конкретном экземпляре."
     Подумайте об этой метафоре в терминах проблемы MoveTo, упомянутой в предыдущем разделе. Вызов Circle.MoveTo может привести только к одному - выполнению MoveTo, ближайшей в объектной иерархии. В этом случае Bee.MoveTo по-прежнему будет вызывать опpед
еление MoveTo для Winged, так как Winged является ближайшим к Bee типом вверх по иерархии. Если предположить, что не определен никакой дочерний тип, котоpый опpеделяет собственный метод MoveTo, переопределяющий MoveTo типа Winged, то любой порожденный по
 отношению к Winged тип будет по-прежнему вызывать тот же самый экземпляр метода MoveTo. Решение может быть принято во время компиляции и это все, что должно быть сделано.
     Однако совсем другое дело, когда метод MoveTo вызывает Show. Каждый тип фигуры имеет свой собственный экземпляр Show, поэтому то, какой экземпляр Show вызывается методом MoveTo, полностью зависит от того, какая реализация объекта вызывает MoveTo. Им
енно поэтому решение о вызове метода Show внутри экземпляра MoveTo должно быть отложено: при компиляции кода MoveTo не может принято никакого решения относительно того, какой метод Show должен быть вызван. Эта информация недоступна во время компиляции, п
оэтому решение должно быть отложено до тех пор, пока программа не начнет выполняться, и пока нельзя будет запросить экземпляр объекта, вызывающий MoveTo.
     Процесс, с помощью которого вызовы статических методов однозначно разрешаются компилятором во время компиляции в один метод, называется ранним связыванием. При раннем связывании вызывающий и вызываемый связываются методы при первой же возможности, т
.е. во время компиляции. При позднем связывании вызывающий и вызываемый методы не могут быть связаны во время компиляции, поэтому включается механизм, позволяющий осуществить связывание несколько позднее, когда вызов действительно произойдет.
     Сущность механизма интересна и тонка, и немного позднее вы увидите, как он работает.
 Совместимость типов объектов

     Наследование до некоторой степени изменяет правила совместимости типов в Турбо Паскале. Помимо всего прочего, порожденный тип наследует совместимость типов всех своих порождающих типов. Эта расширенная совместимость типов принимает три формы:

     - между реализациями объектов;
     - между указателями на экземпляpы объектов;
     - между формальными и фактическими параметрами.

     Однако очень важно помнить, что во всех трех формах совместимость типов расширяется только от потомка к родителю. Другими словами, дочерние типы могут свободно использоваться вместо родительских, но не наоборот.
     В модуле WORKERS.TPU TSalaried является потомком TEmployee, а TCommissioned - потомком TSalaried. Помня об этом, pассмотрим следующие описания:

     type
     PEmployee = ^TEmployee;
     PSalaried = ^TSalfried;
     PCommissioned = ^TCommissioned;
     var
     AnEmployee: TEmployee;
     ASalaried: TSalaried;
     PCommissioned: TCommissioned;
     TEmployeePtr: PEmployee;
     TSalariedPtr: PSalaried;
     TCommissionedPtr: PCommissioned;

     При данных описаниях справедливы следующие операторы присваивания:

     AnEmployee :=ASalaried;
     ASalaried := ACommissioned;
     TCommissionedPtr := ACommissioned;

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

     AnEmployee := ACommissioned;
 только поля Name, Title и Rate из ACommissioned будут скопированы в AnEmployee, т.к. только эти поля являются общими для TCommissioned и TEmployee. Совместимость типов работает также между указателями на типы объектов и подчиняется тем же общим правилам
, что и для реализаций объектов. Указатель на потомка может быть присвоен указателю на родителя. Если дать предыдущие определения, то следующие присваивания указателей будут допустимыми:

     TSalariedPtr := TCommissionedPtr;
     TEmployeePtr := TSalariedPtr;
     TEmployeePtr := PCommissionedPtr;

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

     procedure CalcFedTax(Victim: TSalaried);
 то допустимыми типами фактических параметров могут быть TSalaried или TCommissioned, но не тип TEmployee. Victim также может быть параметром-переменной. При этом выполняются те же правила совместимости.
     Замечание: Имейте в виду, что между параметрами-значениями и параметрами-переменными существует коренное отличие. Параметр-переменная является указателем на действительный, посылаемый в качестве параметра объект, тогда как параметр-переменная являет
ся только копией фактического параметра. Более того, эта копия включает только те поля, которые входят в тип формального параметра-значения. Это означает, что фактический параметр буквально преобразуется к типу формального параметра. Параметр-переменная 
больше напоминает приведение к образцу, в том смысле, что фактический параметр остается неизменным.
     Аналогично, если формальный параметр является указателем на тип объекта, фактический параметр может быть указателем на этот тип объекта или на любой дочерний тип. Пусть дан заголовок процедуры:

     procedure Worker.Add (AWorker: PSalaried);
 тогда допустимыми типами фактических параметров могут быть PSalaried или PCommissioned, но не тип PEmployee.
 Полиморфические объекты

     При чтении предыдущего раздела вы, возможно, задали себе вопрос: "Если любой порожденный от типа параметра тип может передаваться в качестве параметра, то как же пользователь параметра узнает, какой тип объекта он получил?" Фактически, пользователь 
явно этого и не знает. Точный тип фактического параметра не известен во время компиляции. Фактический параметр может быть объектом любого дочернего от параметра-переменной типа, и именно поэтому он называется полиморфическим объектом.
     Теперь, чем же именно хорош полиморфический объект? Прежде всего полиморфические объекты позволяют обрабатывать объекты, чей тип неизвестен на момент компиляции. Это общее замечание настолько ново для образа мышления Паскаля, что пример для вас не п
оявится незамедлительно. (Со временем вы будете удивлены, насколько естественно это выглядит. То есть, когда вы действительно станете объектно-ориентированным программистом.)
     Предположим, что вы написали инструментальное средство для вычерчивания графиков, поддерживающее многочисленные типы фигур: точки, окружности, квадраты, прямоугольники, кривые и т.д. В качестве части этого инструментального средства вы хотите написа
ть программу, которая будет перемещать графическую фигуру по экрану с помощью устройства типа "мышь".
     При старом способе необходимо было написать отдельную процедуру перемещения для каждого типа графической фигуры, поддерживаемой инструментальным средством. Вы должны были бы написать DragButterfly, DragBee, DragMoth и т.д. Несмотря на то, что строга
я типизация (проверка типов) Паскаля позволяла это (и не забывайте, что всегда существуют способы обойти строгую типизацию), различия между типами графических фигур едва ли позволили бы написать действительно общую программу перемещения.
     В конце концов, пчела имеет полоски и жало, бабочка имеет большие цветные кpылья, а стpекоза имеет пеpеливчатые цвета, хвост, да что говорить...
     С этой точки зрения, "сообразительные" программисты, работающие на Турбо Паскале, сделают шаг вперед и скажут: "Поступайте так: передайте запись о кpылатом насекомом процедуре DragIt в качестве ссылки указателя общего вида. В процедуре DragIt провер
яйте свободное поле по фиксированному смещению внутри записи о кpылатом насекомом для определения, какого вида это насекомое, а затем сделайте переход с помощью оператора case:

     case FigureIDTag of
     Bee       : DragBee;
     Butterfly : DragButterfly;
     Dragonfly : DragDragonfly;
     Mocquito  : DragMocquito;
      ...

     Ну, размещение семнадцати маленьких чемоданчиков внутри одного большого является незначительным шагом вперед, но в чем же заключается проблема, ожидающая нас на этом пути?
     Что случится, если пользователь инструментального средства определит несколько новых типов кpылатых насекомых?
     В самом деле, что? Что если пользователь захочет pаботать со сpеднеазиатскими фpуктовыми мухами? В вашей пpогpамме нет типа Fruitfly, поэтому DragIt не содержит метки Fruitfly в операторе case и, следовательно, отвергнет перемещение нового pисунка F
ruitfly. Будучи представленным процедуре DragIt, Fruitfly будет выпадать из оператора case в ветвь else этого оператора как "нераспознанное насекомое".
     Откровенно говоря, создание для продажи инструментального средства без исходного кода страдает этой проблемой. Инструментальное средство может работать только с типами данных, которые "известны" ему, т.е. которые определены разработчиком инструмента
льного средства. Пользователь инструментального средства оказывается бессильным перед расширением его функций в направлении, не предвиденном разработчиком. То, что пользователь купил, то он и получил. И точка.
     Выходом из проблемы является использование правил расширенной совместимости типов Турбо Паскаля для объектов и разработка прикладных программ с использованием полиморфических методов. Если процедура DragIt инструментального средства установлена так,
 что может работать с полиморфическими объектами, то она будет работать с любыми объектами, определенными в инструментальном средстве, и с любыми дочерними объектами, которые вы определите сами. Если типы объектов инструментального средства используют ви
ртуальные методы, то объекты и программы инструментального средства могут работать со сделанными вами графическими фигурами в собственных терминах самих фигур. Определенный вами сегодня виртуальный метод может вызываться файлом модуля .TPU инструментальн
ого средства, который был написан и оттранслирован год назад. Объектно-ориентированное программирование дает такую возможность, а виртуальные методы являются ключом к ней.
     Понимание того, как виpтуальные методы делают возможными такие вызовы полимоpфических методов тpебует пояснения описания и использования виpтуальных методов.
 Виртуальные методы

     Метод становится виртуальным, если за его объявлением в типе объекта стоит новое зарезервированное слово virtual. Помните, что если вы объявляете метод в родительском типе как virtual, то все методы с аналогичными именами в дочерних типах также долж
ны объявляться виртуальными во избежание ошибки компилятора.
     Ниже приведены знакомые вам объекты из пpимеpа платежной ведомости, должным образом виртуализированные:

     type
     PEmployee = ^TEmployee;
     TEmployee = object
       Name, Title: string[25];
       Rate: Real;
       constructor Init (AName, ATitle: String; ARate: Real);
       function GetPayAmount : Real; virtual;
       function GetName : String;
       function GetTitle : String;
       function GetRate : Real;
       procedure Show; virtual;
     end;

     PHourly = ^THourly;
     THourly = object(TEmployee);
       Time: Integer;
       constructor Init (AName, ATitle: String; ARate: Real;
            Time: Integer);
       function GetPayAmount : Real; virtual;
       function GetTime : Integer;
     end;

     PSalaried = ^TSalaried;
     TSalaried = object(TEmployee);
       function GetPayAmount : Real; virtual;
     end;

     PCommissioned = ^TCommissioned;
     TCommissioned = object(Salaried);
       Commission : Real;
       SalesAmount : Real;
       constructor Init (AName, ATitle: String;
           ARate, ACommission, ASalesAmount: Real);
       function GetPayAmount : Real; virtual;
     end;

     А ниже пpиводится пpимеp для насекомых, дополненный виpтуальными методами.

     type
     Winged = object(Insect)
       constructor Init (AX, AY : Integer)
       procedure Show; virtual;
       procedure Hide; virtual;
     end;

     Прежде всего обратите внимание, что метод MoveTo, показанный для типа Bee, теперь удален из его определения. Теперь типу Bee больше нет нужды переопределять метод MoveTo типа Winged с помощью немодифицируемой копии, компилируемой в его собственной о
бласти действия. Вместо этого, MoveTo теперь может наследоваться от Winged со всеми вложенными в MoveTo вызовами, которые, однако, будут вызывать методы из Bee, а не из Winged, как это происходило в полностью статической иерархии объектов.
     Отметьте также новое зарезервированное слово constructor (конструктор), заменившее зарезервированное слово procedure для Winged.Init и Bee.Init. Конструктор является специальным типом процедуры, которая выполняет некоторую установочную работу для ме
ханизма виртуальных методов. Более того, конструктор должен вызываться перед вызовом любого виртуального метода. Вызов виртуального метода без предварительного вызова конструктора может привести к блокированию системы, а у компилятора нет способа провери
ть порядок вызова методов.
     Каждый тип объекта, имеющий виртуальные методы, обязан иметь конструктор.
     Замечание: Конструктор должен вызываться перед вызовом любого другого виртуального метода. Вызов виртульного метода без предыдущего обращения к конструктору может вызвать блокировку системы, и компилятор не сможет проверить порядок, в котором вызыва
ются методы.
     Замечание: Каждый отдельный экземпляр объекта должен инициализироваться отдельным вызовом конструктора. Недостаточно инициализировать один экземпляр объекта и затем присваивать этот экземпляр другим. Другие экземпляры, даже если они могут содержать 
правильные данные, не будут инициализированы оператором присваивания и заблокируют систему при любых вызовах их виртуальных методов. Например:

     var
       FBee, GBee: Bee;    { создать два экземпляра Bee }
     begin
       FBee.Init(5, 9)  { вызов конструктора для FBee }
       GBee := FBee;    { Gbee  недопустим! }
     end;

     Что же именно создает конструктор? Каждый тип объекта содержит нечто, называемое таблицей виртуального метода (ТВМ) в сегменте данных. ТВМ содержит размер типа объекта и для каждого виртуального метода указатель на код, выполняющий данный метод. Кон
структор устанавливает связь между вызывающей его реализацией объекта и ТВМ типа объекта.
     Важно помнить, что имеется только одна ТВМ для каждого типа объекта. Отдельные экземпляры типа объекта (т.е. переменные этого типа) содержат только соединение с ТВМ, но не саму ТВМ. Конструктор устанавливает значение этого соединения в ТВМ. Именно б
лагодаря этому вы нигде не можете запустить выполнение перед вызовом конструктора.
 Проверка диапазонов при вызове виртуальных методов

     В процессе разработки программы вам, возможно, захочется повысить меры безопасности, которая снижается из-за вызовов виртуальных методов Турбо Паскаля. Если директива $R находится во включенном состоянии, {$R+}, то все вызовы виртуальных методов буд
ут проверяться на состояние инициализации для выполняющих вызовы реализаций. Если выполняющая вызов реализация еще не была инициализирована конструктором, то произойдет ошибка проверки диапазона исполняющей системы.

     Примечание: Состоянием по умолчанию является {$R-}.

     После того, как вы хорошенько перетрясли программу и удостоверились, что отсутствуют вызовы методов из неинициализированных реализаций, вы можете до некоторой степени ускорить выполнение программы путем переключения директивы $R в пассивное состояни
е. После этого проверка вызовов методов из неинициализированных реализаций осуществляться не будет, что оставляет вероятность блокировки системы, если будет выявлена такая ошибка.
 Что виpтуально, то всегда виpтуально

     Вы уже вероятно обратили внимание, что как Winged, так и Bee содержат методы, называемые Show и Hide. Все заголовки методов для Show и Hide объявлены виртуальными и снабжены зарезервированным словом virtual. Как только родительский тип объекта объяв
ляет метод виртуальным, все его потомки также должны объявить этот метод виртуальным. Другими словами, статический метод никогда не сможет переопределить виртуальный метод. Если вы попытаетесь сделать это, то компилятор выдаст собщение об ошибке.
     Также следует помнить, что после того, как метод стал виртуальным, его заголовок не может изменяться в объектах более низкого уровня иерархии. Вы можете представлять себе каждое определение виртуального метода как ворота для всех их. Исходя из этих 
соображений, заголовки всех реализаций одного и того же виртуального метода должны быть идентичными, включая число параметров и их типы. Это не относится к статическим методам: статический метод, переопределяющий другой, может иметь отличное число параме
тров и типы этих параметров, в зависимости от необходимости.
     Это целый новый миp.
 Возможности расширения объектов

     Важным замечанием, касающимся модулей типа WORKERS.PAS, является то, что типы объектов и методы, определенные в модуле, могут поставляться пользователю в форме .TPU, т.е. в форме, способной к непосредственной компоновке, без исходного кода. (Нужно п
росмотреть только листинг интерфейсной части модуля.) Используя полиморфические объекты и виртуальные методы, пользователь файла .TPU сможет добавлять характеристики для приспособления модуля к своим нуждам.
     Новое понятие о добавлении функциональных характеристик в программу без предоставления ее исходного кода называется способностью к расширению. Способность к расширению является естественным следствием наследования: вы наследуете все, чем обладают по
рождающие типы, а затем добавляете новые нужные вам возможности. Позднее связывание позволяет, чтобы новое связывалось со старым во время выполнения программы, благодаря чему расширение существующего кода выглядит "бесшовным" и стоит вам в части выполнен
ия не более, чем быстрое путешествие по таблице виртуального метода.
 Статические методы или виртуальные методы?

     В общем случае, вам следует делать методы виртуальным. Используйте статические методы только в том случае, если вы хотите получить оптимальную эффективность скорости выполнения и использования памяти. Однако в этом случае, как вы видели, вы теряете 
возможности расширения.
     Предположим, что вы описываете объект с именем Ancestor и внутри этого объекта вы описываете метод с именем Action. Как вы определяете, каким должен быть метод, виртуальным или статическим? Здесь приводится правило большого пальца: сделайте метод Ac
tion виртуальным, если имеется вероятность, что будущие наследники объекта Ancestor будут переопределять Action, а вы хотите, чтобы будущий код был доступен Ancestor.
     С другой стороны, помните, что если у объекта имеются любые виртуальные методы, то для этого объекта в сегменте данных будет создана таблица виртуальных методов (ТВМ) и каждый экземпляр этого объекта будет иметь связь с ТВМ. Каждый вызов виртуальног
о метода должен проходить через ТВМ, тогда как статические методы вызываются непосредственно. Хотя просмотр ТВМ является весьма эффективным, вызов статического метода все равно остается немного более быстрым, чем вызов виртуального. И если в вашем объект
е нет виртуальных методов, то и ТВМ отсутствует в сегменте данных и (что более важно) в каждом экземпляре объекта отсутствуют связи с ТВМ.
     Дополнительная скорость и эффективное использование памяти для статических методов должно уравновешиваться гибкостью, которую допускают виртуальные методы: вы можете расширить имеющийся код спустя много времени после его компиляции. Помните, что пол
ьзователь вашего типа объекта может рассматривать пути его использования, которые вам и не снились, что, в конечном счете, имеет основное значение.
 Динамические объекты

     Все приведенные до сих пор объекты имели статические реализации типов объектов, которым в объявлении var присваивались имена и которые размещались в сегменте данных или в стеке.

     var
       ASalaried: TSalaried;

     Примечание: Использование здесь слова "статический" не имеет отношения с статическим методам.
     Объекты могут размещаться в динамической памяти и ими можно манипулировать с помощью указателей, как и с тесно связанными с ними типами записей, что всегда имело место в Паскале. Турбо Паскаль включает несколько мощных расширений для выполнения дина
мического размещения и удаления объектов более легкими и более эффективными способами.
     Объекты могут размещаться, как области памяти, на которые ссылается указатель, с помощью процедуры New:

     var
     CurrentPay: Real;
     P: ^TSalaried;
     New(P);
     ...

     Как и для типов записей, процедура New выделяет в динамической памяти пространство, достаточное для размещения реализации указателя базового типа и возвращает адрес этого пространства в указателе.
     Если динамический объект содержит виртуальные методы, то он должен инициализироваться с помощью вызова конструктора перед тем, как будет вызван любой из его методов:

     P^.Init('Sara Adams', 'Account manager', 2400);

     Затем вызовы методов могут происходить в обычном порядке, с использованием имени указателя и ссылочного символа вместо имени реализации, которое использовалось бы при обращении к статически размещенному объекту:

     CurrentPay := P^.GetPayAmount;
 Размещение и инициализация с помощью процедуры New

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

     New(P, Init('Sara Adams', 'Account manager', 2400));

     Если для процедуры New используется расширенный синтаксис, то конструктор Init действительно выполняет динамическое размещение, используя специальный код входа, сгенерированного как часть компиляции конструктора. Имя реализации не может предшествова
ть Init, т.к. в то время, когда процедура New вызвана, реализация, инициализируемая с помощью Init, еще не существует. Компилятор идентифицирует правильный вызываемый метод Init посредством типа указателя, пересылаемого в качестве первого параметра.
     Процедура New также была расширена для возможности использования ее как функции, которая возвращает значение указателя. Посылаемый New параметр является типом указателя на объект, а не самой переменной-указателем:

     type
     PSalaried = ^TSalaried;

     var
     P: PSalaried;
     begin
     P := New(PSalaried);
      ...

     Обратите внимание, что в данной версии Паскаля функциональная форма расширения процедуры New применима ко всем типам данных, а не только к типам объектов.
     Функциональная форма New, как и процедурная форма, также может воспринимать конструктор объектного типа в качестве второго параметра:

     P := New(PSalaried, Init('Sara Adams', 'Account manager', 2400));

     В Турбо Паскале осуществлено также параллельное расширение процедуры Dispose, это подробно обсуждается в следующем разделе.
     Примечание: Новая стандартная процедура Fail поможет вам в конструкторах выполнить восстановление при ошибке (см. главу 17 в "Руководстве программиста").
 Удаление динамических объектов

     Также, как и обычные записи Паскаля, размещаемые в динамически распределяемой области памяти, объекты могут удаляться процедурой Dispose, если они больше не нужны:

     Dispose (P);

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

     MyComplexObject.Done;

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

     Для очистки и удаления динамически размещенного объекта Турбо Паскаль предоставляет специальный тип метода, называемый "сборщиком мусора" или деструктором. Деструктор объединяет шаг удаления объекта с какими-либо другими действиями или задачами, нео
бходимыми для данного типа объекта. Для единственного типа объекта можно определить несколько деструкторов.
     Деструктор определяется совместно со всеми другими методами объекта в определении типа объекта:

     TEmployee = object
       Name: string[25];
       Title: string[25];
       Rate: Real;
       constructor Init(AName, ATitle: String; ARate: Real);
       destructor Done; virtual;
       function GetName: String;
       function GetTitle: String;
       function GetRate: Rate; virtual;
       function GetPayAmount: Real; virtual;
     end;

     Деструкторы можно наследовать, и они могут быть либо статическими, либо виртуальными. Поскольку различные программы завершения обычно требуют различные типы объектов, мы рекомендуем, чтобы деструкторы всегда были виртуальными, благодаря чему для каж
дого типа объекта будет выполнен правильный деструктор.
     Запомните, что зарезервированное слово destructor не требуется указывать для каждого метода очистки, даже если определение типа объекта содержит виртуальные методы. Деструкторы в действительности работают только с динамически размещенными объектами.
 При очистке динамически размещенного объекта, деструктор осуществляет специальные функции: он гарантирует, что в динамически распределяемой области памяти всегда будет освобождаться правильное число байтов. Не может быть никаких опасений по поводу испол
ьзования деструктора применительно к статически размещенным объектам; фактически, не передавая типа объекта деструктору, вы лишаете объект данного типа полных преимуществ управления динамической памятью в Турбо Паскале.
     Деструкторы в действительности становятся самими собою тогда, когда должны очищаться полиморфические объекты и когда должна освобождаться занимаемая ими память. Полиморфические объекты - это те объекты, которые были присвоены родительскому типу благ
одаря правилам совместимости расширенных типов Турбо Паскаля. Экземпляp объекта типа THourly, пpисвоенный переменной типа TEmployee, является примером полиморфического объекта. Эти правила также могут быть применены к объектам; указатель на THourly может
 свободно быть присвоен указателю на TEmployee, а указуемый этим указателем объект опять же будет полиморфическим объектом.
     Термин "полиморфический" является подходящим, так как код, обрабатывающий объект, не знает точно во время компиляции, какой тип объекта ему придется в конце концов обработать. Единственное, что он знает, это то, что этот объект принадлежит иерархии 
объектов, являющихся потомками указанного типа объекта.
     Очевидно, что размеры типов объектов отличаются. Поэтому, когда наступает время очистки размещенного в динамической памяти полиморфического объекта, то как же Dispose узнает, сколько байт динамического пространства нужно освобождать? Во время компил
яции из полиморфического объекта нельзя извлечь никакой информации относительно размера объекта.
     Деструктор разрешает эту головоломку путем обращения к тому месту, где эта информация записана: в ТВМ переменных реализаций. В каждой ТВМ типа объекта содержится размер в байтах данного типа объекта. Таблица виртуальных методов любого объекта доступ
на посредством скрытого параметра Self, посылаемого медоду при вызове метода. Деструктор является всего лишь разновидностью метода и поэтому, когда объект вызывает его, деструктор получает копию Self через стек. Таким образом, если объект является полимо
рфическим во время компиляции, он никогда не будет полиморфическим во время выполнения благодаря позднему связыванию.
     Для выполнения этого освобождения памяти при позднем связывании деструктор нужно вызывать, как часть расширенного синтаксиса процедуры Dispose:

     Dispose(P, Done);

     (Вызов деструктора вне процедуры Dispose вообще не выполняет никакого освобождения памяти.) Здесь происходит на самом деле то, что сборщик мусора объекта, на который указывает P, выполняется как обычный метод. Однако, как только последнее действие в
ыполнено, деструктор ищет размер реализации своего типа в ТВМ и пересылает размер процедуре Dispose. Процедура Dispose завершает процесс путем удаления правильного числа байт пространства динамической памяти, которое (пространство) до этого относилось к 
P^. Число освобождаемых байт будет правильным независимо от того, указывал ли P на экземпляp типа TSalaried, или он указавал на один из дочерних типов типа TSalaried, напpимеp, на TCommissioned.
     Заметьте, что сам по себе метод деструктора может быть пуст и выполнять только эту функцию:

     destructor AnObject.Done;
     begin
     end;

     То, что делается полезного в этом деструкторе, не является достоянием его тела, однако при этом компилятором генерируется код эпилога в ответ на зарезервированное слово destructor. Это напоминает модуль, который ничего не экспортирует, но который ос
уществляет некоторые невидимые действия за счет выполнения своей секции инициализации перед стартом программы. Все действия происходят "за кулисами".
 Пример размещения динамического объекта

     Последний пример программы даст вам возможность приобрести некоторые навыки в использовании размещенных в динамической памяти объектов, включая использование для удаления объекта деструктора. Программа показывает, как в динамической памяти может быт
ь создат связанный список рабочих объектов и как он за ненадобностью может быть очищен при помощи деструктора.
     Построение связанного списка объектов требует, чтобы каждый объект содержал указатель на следующий объект списка. Тип TEmployee не содержит таких указателей. Простым выходом из этой ситуации было бы добавление указателя в TEmployee , благодаря чему 
можно быть уверенным, что все потомки TEmployee наследуют такой указатель. Однако, добавление чего-либо в TEmployee требует от вас наличия исходного кода, а как говорилось ранее, одним из преимуществ объектно-ориентированного программирования является во
зможность расширения объектов без необходимости их перекомпиляции.
     Решение, которое не требует никаких изменений TEmployee, создает новый тип объекта, не являющийся потомком TEmployee. Тип StaffList представляет собой очень простой объект, целью которого является создание заголовков для объектов типа TEmployee. Так
 как TEmployee не содержит никаких указателей на следующий объект в списке, то простой тип записи TNode осуществляет этот сервис. TNode даже проще, чем StaffList в том, что TNode не является объектом, не содержит ни одного метода и не имеет никаких данны
х, за исключением указателя на тип TEmployee и указателя на следующий узел списка.
     TStaffList содержит метод, который позволяет ему добавлять нового pабочего в связанный список записей TNode путем внесение нового экземпляра TNode непосредственно после самого себя в качестве указуемого с помощью указателя поля TNodes. Метод Add при
нимает указатель на объект типа TEmployee, но не сам объект. Из-за расширенной совместимости типов Турбо Паскаля указатели на любого потомка типа TEmployee также должны передаваться в TList.Add в параметре Item.
     Программа WorkList описывает статическую переменную Staff типа TStaffList и строит связанный список из пяти узлов. Каждый узел указывает на отдельный pабочий объект, который является либо TEmployee, либо одним из его потомков. Перед созданием каждог
о динамического объекта и после того, как объект создан, возвpащает число байт свободной динамической памяти. Наконец, полная структура, включающая пять записей TNode и пять объектов типа TEmployee, очищается и удаляется из динамической памяти с помощью 
одного вызова дестpуктоpа статического объекта Staff типа TStaffList.

             і
     List    і    Node         Node         Node
    ЪДДДДДї  і   ЪДДДДДї      ЪДДДДДї      ЪДДДДДї
    і     і  і   і     і      і     і      і     і
    і   ДДДДДД і   ДДДДДД і   ДДДДДД і   ДДДДДДї
    і     і  і   і і   і      і і   і      і і   і    і
    АДДДДДЩ  і   АДіДДДЩ      АДіДДДЩ      АДіДДДЩ    і
             і     і            і            і        
             і                             
             і ЪДДДДДДДДДї   ЪДДДДДДДДДї   ЪДДДДДДДДДї
             і і  Name   і   і  Name   і   і  Name   і
             і ГДДДДДДДДДґ   ГДДДДДДДДДґ   ГДДДДДДДДДґ
             і і  Title  і   і  Title  і   і  Title  і
             і ГДДДДДДДДДґ   ГДДДДДДДДДґ   ГДДДДДДДДДґ
             і і  Rate   і   і  Rate   і   і  Rate   і
             і ГДДДДДДДДДґ   ГДДДДДДДДДґ   ГДДДДДДДДДґ
   Сегмент   і і         і   і         і   і         і
   данный    і АД Д Д Д ДЩ   АД Д Д Д ДЩ   АД Д Д Д ДЩ
   (стати-   і  Динамически распределяемая область
   ческий)   і  памяти (динамический)


     Рис. 4.2 Схема структур данных программы WorkList

 Удаление сложной структуры данных из динамической памяти

     Деструктор Staff.Done стоит того, чтобы рассмотреть его внимательно. Уничтожение объекта TStaffList включает удаление трех различных типов структур: полиморфических объектов pабочих структур в списке, записей TNode, поддерживающих список, и (если он
 размещен в динамической памяти) объект TList, который озаглавливает список. Весь процесс запускается путем единственного вызова деструктора объекта TStaffList:

     Staff.Done;

     Код деструктора заслуживает более подробного изучения:

     destructor StaffList.Done;
     var
      N: TNodePtr;
     begin
         while TNodes <> nil do
     begin
         N := TNodes;
         Dispose(N^.Item, Done);
         TNodes := N^.Next;
         Dispose (N);
     end;
     end;

     Список очищается начиная с "головы" списка с помощью алгоритма "из руки в руку", который до некоторой степени напоминает дерганье за веревку воздушного змея: два указателя (указатель TNodes внутри Staff и рабочий указатель N) изменяют свои ссылки в 
списке, тогда как первый элемент списка удаляется. Вызов процедуры Dispose освобождает память, занимаемую первым объектом TEmployee в списке (Item^), затем TNodes продвигается на следующую запись списка с помощью оператора TNodes := N^.Next, сама запись 
TNode удаляется, и процесс продолжается до полного очищения списка.
     Важным моментом в деструкторе Done является способ, которым удаляются из списка объекты TEmployee:

     Dispose(N.Item, Done);

     Здесь N.Item является первым объектом TEmployee в списке, а вызываемый метод Done является деструктором этого объекта. Запомните, что действительный тип N^.Item^ не обязательно является типом TEmployee, однако он может быть любым дочерним типом типа
 TEmployee. Очищаемый объект является полиморфическим и поэтому нельзя сделать никаких предположений относительно его действительного размера или точного его типа на этапе компиляции. В приведенном выше вызове Dispose, как только Done выполнит все содерж
ащиеся в нем операторы, "невидимый" код эпилога ищет размер реализации очищаемого объекта в ТВМ этого объекта. Метод Done передает размер процедуре Dispose, которая затем освобождает точное количество динамической памяти, в действительности занимаемой по
лиморфическим объектом.
     Помните, что если должно освобождаться правильное количество динамической памяти, то полиморфический объект должен очищаться только посредством вызова передаваемого Dispose деструктора.
     В примере программы Staff объявляется как статическая переменная в сегменте данных. Staff мог бы столь же легко разместиться в динамической памяти и "прикрепиться к реальному миру" посредством указателя типа ListPtr. Если заголовок списка также явля
ется динамическим объектом, то удаление структуры можно осуществить путем вызова деструктора, выполняющегося внутри Dispose:

     var
       Staff: TStaffListPtr;
     begin
       Dispose(Staff, Done);
       ...

     Здесь процедура Dispose вызывает метод деструктора Done для очистки структуры в динамической памяти. Затем, когда Done завершается, Dispose освобождает память, на которую указывает Staff, удаляя, как пpавило, из динамической памяти также и заголовок
 списка.
     Программа WORKLIST.PAS (находящаяся на вашем диске) использует тот же модуль WORKERS.PAS, что и pаньше Она создает объект List, являющийся оглавлением связанного списка из пяти полиморфических объектов, совместимых с TEmployee, а затем удаляет всю д
инамическую структуру данных с помощью единственного вызова деструктора Staff.Done.
 Что же дальше?

     Как и во всяком другом аспекте машинного программирования, вы не преуспеете в объектно-ориентированном программировании, если будете только читать о нем, но вы добъетесь результата, если начнете программировать. Большинство людей при первом столкнов
ении с объектно-ориентированном программированием начинают бормотать с придыханием; "Я не могу постичь этого". "Ага!" приходит позднее, ночью, когда целостная концепция является к нам в одно прекрасное мгновение, и мы, побросав свои никчемные дела, испол
ьзуем это мгновение для обращения к богу. Как лицо женщины, возникающее из чернильных пятен Роша, то, что до этого было смутным, становится очевидным и затем легким.
     Самое лучшее, что вы можете сделать в качестве первого шага в объектно-ориентированном программировании, так это взять модуль WORKERS.PAS (он находится на вашем диске) и расширить его. Как только вы воскликните "Ага!", начинайте строить ориентирован
ные на объекты концепции в вашей повседневной программистской жизни. Возьмите несколько имеющихся утилит, которые вы используете каждый день, и переосмыслите их в ориентированных на объекты терминах. Посмотрите критически на "овощное рагу" вашей библиоте
ки процедур и попытайтесь найти в них объекты, затем перепишите процедуры в объектной форме. Вы убедитесь, что библиотеки объектов станет намного легче использовать в будущих проектах. Даже самые незначительные ваши начальные инвестиции в программные уси
лия станут навсегда излишними. У вас едва ли возникнет необходимость переписывать объект с самого начала. Если он работает как надо, то используйте его. Если объекту чего-либо не хватает, то расширьте его. Но если он работает хорошо, то нет смысла выбрас
ывать из него что-либо.
 Выводы

     Объектно-ориентированное программирование является прямым следствием усложнения современных приложений, усложнения, которое часто заставляет многих программистов в отчаянии вскидывать вверх руки. Наследование и инкапсуляция являются максимально эффе
ктивными средствами для управления сложностью. (Существует разница между десятью тысячами насекомых, расклассифицированных по таксономической схеме, и десятью тысячами насекомых, жужжащих возле ваших ушей.) Представляя собой значительно большее, чем прос
то структурное программирование, объектно-ориентированное программирование вносит рациональный порядок в структуру программного обеспечения ЭВМ, что, как и таксономическая схема, устанавливает порядок, не устанавливая пределов.
     Добавьте сюда перспективы возможности расширения и повторному использования существующего кода и все это начнет звучать настолько хорошо, что будет походить на правду. Вы думаете, что это невозможно? Но это же Турбо Паскаль! Слово "невозможно" в нем
 не определено.






Яндекс цитирования