Назад Домой! Дальше Глава 14. ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ
В TURBO PASCAL 7.0


Объектно-ориентированное программирование (ООП) представляет собой новый этап развития 
современных концепций построения языков программирования. Здесь получили дальнейшее развитие 
принципы структурного программирования - структуризация программ и данных, модульность 
и т. д.
В основе ООП лежит понятие объекта (object), сочетающего в себе данные и действия над ними. 
Объект в некотором роде похож на стандартный тип-запись (record), но включает в себя не 
только поля данных, но также и подпрограммы для обработки этих данных, называемые методами. 
Таким образом, в объекте сосредоточены его свойства и поведение. Идеи создания нового 
типа-объект были уже заложены при введении процедурного типа, отождествляющего между собой
данные и действия над ними. Фактически тип-объект включает в себя помимо данных элементы 
процедурных типов, правда, несколько иначе оформленные и с расширенным набором особенностей, 
о которых будет сказано ниже.

Введение нового типа данных потребовало пересмотреть некоторые концепции языка Паскаль: 
ввести новые понятия, как, например, инкапсуляция, наследование, полиморфизм и виртуальность,
новые зарезервированные слова (constructor, destructor, inherited, object, private, public, 
virtual), изменить уже существующие подпрограммы (подпрограммы New и Dispose).
ООП характеризуется тремя основными свойствами: инкапсуляция (encapsulation), наследование 
(inheritance) и полиморфизм (polymorphism).

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

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

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

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

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

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

Однако следует иметь в виду, что ООП обладает и рядом недостатков и эффективно не во всех 
случаях. Как правило, использование ООП приводит к уменьшению быстродействия программы, 
особенно в тех случаях, когда используются виртуальные методы (см. п. 14.3). Неэффективно 
ООП применительно к небольшим программам, поэтому его можно рекомендовать при создании 
больших программ, а лучше даже класса программ (как, например, создание интерактивных 
программ с использованием Turbo Vision, где основой является ООП). Можно, по-видимому, даже 
сказать, что ООП скорее не упрощает саму программу, а упрощает технологию ее создания.

14.1. Пример использования ООП
В связи с тем, что использование ООП применительно к небольшим программам неэффективно, 
возникает трудность иллюстрации тех или иных его положений. Тривиальные примеры могут 
создать впечатление искусственности ООП, его неэффективности, сложные примеры потребуют 
много усилий для уяснения принципов работы той или иной программы. В связи с этим мы решили 
привести лишь один пример сравнительно сложной программы (см. приложение Г) - экранного 
редактора символьной информации, на котором будут иллюстрироваться все моменты ООП. Т. к. 
такие редакторы довольно сложны, приведена только его часть, выполняющая небольшое количество 
фунций. В ней совершенно не рассмотрен вопрос работы с внешними файлами, куда записывается 
набранная в редакторе информация, ограничено число обрабатываемых редактором клавиш. Далее 
будет сказано, как, используя принципы ООП, можно расширить его функции.. Редактор 
предназначен только для режима дисплея, когда на экране размещается 25 строк по 80 символов 
в строке. Не следует рассматривать приведенную программу как высококачественный редактор - 
это учебный пример, и он не является лучшим. В частности, программа неудовлетворительна с 
точки зрения быстродействия. Более того, в некоторых случаях выбиралась далеко не оптимальная 
реализация с целью проиллюстрировать те или иные положения ООП, без которых можно было бы и 
обойтись. 

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

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

Вторая группа начинается объектом "Операция", включающим в себя общие действия по обработке 
нажатия любой клавиши (или комбинации клавиш). Далее идут объекты (группы объектов) обработки 
кодов тех или иных клавиш. В данном примере рассмотрены лишь объекты обработки кодов клавиш, 
формирующих изображаемые символы (символы с кодом больше 31), клавиш направления (Up, Dn, 
Left, Right), клавиши формирования новой строки (Enter) и клавиши удаления символа или 
объединения строк (BasckSpace). 

Количество объектов обработки кодов клавиш можно в принципе увеличивать.
В этой иерархии предшествующие объекты "порождают" последующие и передают им часть своих 
свойств. Например, объект "Структура" включает в себя свойства объекта "Связь элементов", 
т. к. несет информацию и о связи конкретных элементов структуры с предыдущими и последующими 
элементами.

14.2. Понятие объекта
Основным понятием ООП и элементом программы является объект, сочетающий в себе как 
совокупность данных, так и действий над ними. Тип-объект в Turbo Pascal напоминает 
тип-запись, однако вместо зарезервированного слова record используется слово object, а кроме 
полей, представляющих данные, в нем перечислены и заголовки подпрограмм, называемых методами. 
При задании такого типа после зарезервированного слова object перечисляются все поля объекта 
и заголовки методов, после чего пишется слово end. Так, в рассматриваемом примере 
используется тип tConnection (связь элементов):

type
tConnection = object
PredElem: Pointer,
NextElem: Pointer;
procedure PutPredElem(PredEl: Pointer);
procedure PutNextElem(NextEl: Pointer);
function GetPredElera: Pointer;
function GetNextElem: Pointer 
end;

В этом типе PredElem и NextElem - указатели на предыдущий и последующий элементы в структуре 
(если соответствующего элемента нет, указатель имеет значение nil). Используются указатели 
типа Pointer, т. к. элементы могут быть различными: они могут быть и элементами строки, и 
строками. Далее идут заголовки двух процедур и двух функций, позволяющих либо задавать, либо 
получать значения указателей объекта.

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

procedure tConnection.PutPredElem; 
begin
PredElem:=PredEl
end;

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

Такие объекты называются абстрактными, и переменных таких типов в программе, как правило, не 
бывает.

14.2.1. Инкапсуляция
Под термином "инкапсуляция" понимается совмещение в одном объекте как параметров, так и 
действий над ними. При этом включенные в объект подпрограммы (методы), как правило, оперируют 
с данными этого объекта или обращаются к методам объектов-предков (см. п. 14.2.2). Это 
позволяет объединить в одном месте все свойства объекта, что облегчает понимание работы 
программы, ее отладку, модификацию. Так, например, все свойства связей между элементами в 
структуре текста сосредоточены в типе tConnection. Как правило, к данным объекта извне 
непосредственно не обращаются, хотя это и возможно. 

Для обращения к данным обычно используют соответствующие методы. Так, в рассматриваемом 
примере для этой цели служат четыре метода PutPredElem, PutNextElem, GetPredElem и 
GetNextElem, с помощью которых можно задавать и получать значения указателей на предыдущий и 
последующий элемент. Это обстоятельство не является надуманным. В нашей повседневной жизни 
так обычно и происходит - мы используем те или иные параметры опосредованно. Если взять уже 
упоминавшийся нами пример с компьютером, то у него есть такой параметр, как размер свободной 
памяти на жестком диске. Однако вряд ли владелец компьютера для определения этого параметра 
будет непосредственно отсчитывать байты -для этой цели служат специальные подпрограммы.
Такое опосредованное обращение к данным позволяет избежать во многих случаях непредвиденных 
нежелательных изменений параметров. В Turbo Pascal с этой целью используется специальное 
зарезервированное слово private (приватный), в принципе запрещающее непосредственное 
обращение к тем или иным данным и методам объекта вне модуля, в котором описан объект. 

В версии 7.0 приватная секция может размещаться в любом месте объекта (раньше - только в 
конце после обычных, доступных параметров и методов). Так, если необходимо запретить из 
основной программы обращаться к данным объекта типа tConnection (напомним, что основная 
программа редактора находится в отдельном файле), этот тип можно описать следующим образом: 
type
tConnection = object
procedure PutPredElem(PredEl: Pointer);
procedure PutNextElem(NextEl: Pointer);
function GetPredElem: Pointer;
function GetNextElem: Pointer
private
PredElem: Pointer;
NextElem: Pointer; 
end;

Если приватная секция находится не в конце объекта, то для ограничения диапазона действия 
зарезервированного слова private следует после приватной секции поместить зарезервированное 
слово public (доступный извне) - только в версии 7.0:

type
tConnection = object 
private
PredElem: Pointer; 
NextElem: Pointer; 
public
procedure PutPredElem(PredEl: Pointer); 
procedure PutNextElem(NextEl: Pointer); 
function GetPredElem: Pointer; 
function GetNextElem: Pointer 
end;
 
14.2.2. Наследование
Если из рассматриваемого примера взять тип Structure (структура), то структуру текста можно 
задать ее начальным и конечным элементами и связями между отдельными элементами структуры. 
Связи между отдельными элементами задаются типом tConnection, и было бы нецелесообразно при 
создании нового типа tStructure заново задавать эти связи. Чтобы этого избежать, в ООП 
заложено свойство наследования характеристик одного объекта другим. Для этого один из 
объектов объявляется потомком другого, который, в свою очередь, становится предком этого 
нового объекта. Потомок наследует все параметры своего предка и его методы, поэтому вторично 
их описывать нет необходимости, а использовать можно. Это существенно упрощает запись схожих 
объектов, если установить между ними наследственную связь.
В примере редактора используемые объекты образуют наследственную структуру, которая 
изображена на рис. 2. В частности, когда задается тип tStructure, его можно объявить 
потомком типа tConnection (для этого следует после зарезервированного слова object в круглых 
скобках указать имя типа-предка):

type
tStructure = object(tConnection)   {tStructure — потомок tConnection}
FirstElem: Pointer;
LastElem: Pointer;
constructor Init;
procedure PutFirstElem(FirstEl: Pointer);
procedure PutLastElem(FirstEl: Pointer);
function GetFirstElem: Pointer;
function GetLastElem: Pointer;
procedure InitElem(var NewPoint: Pointer); virtual;
procedure DispElem(PointDel: Pointer); virtual;
procedure PutConnection(FirstPoint, SecondPoint: Pointer);
procedure NewEl(PointPredEl, PointNextEl: Pointer);
procedure DelEl(PointDel: Pointer); 
end;

В этом типе имеются собственные данные: FirstElem (указатель на первый элемент структуры), 
LastElem (указатель на последний элемент структуры) и методы: Init (инициализация структуры), 
PutFirstElem (задание значения указателя на первый элемент), PutLastElem (задание значения 
указателя на последний элемент, GetFirstElem (получение значения указателя на первый 
элемент), GetLastElem (получение значения указателя на последний элемент), InitElem 
(инициализация - создание нового элемента), DispElem (удаление элемента), PutConnection 
(задание связей между двумя элементами), NewEl (помещение в структуру нового элемента), 
DelEl (удаление из структуры элемента). Кроме них этот тип наследует от предка tConnection 
его данные PredElem и NextElem и методы PutPredElem, PutNextElem, GetPredElem и GetNextElem. 
В Turbo Pascal непосредственный предок может быть только один. Однако он, в свою очередь, 
может быть потомком другого типа и т. д. В этом случае потомок наследует характеристики всех 
своих предков. 

Так, например, тип tLine (строка) является потомком типа tStructure, который, 
в свою очередь, является потомком типа tConnection. В связи с этим объект типа tLine может 
использовать параметры и методы в том числе и типа tConnection.

14.2.3. Полиморфизм
В рассматриваемом примере может возникнут необходимость помещения того или иного символа в 
элемент строки, строку или весь текст. Для этого можно включить соответствующие методы в 
типы tElLine (элемент строки), tLine (строка) и tText (текст). Естественно, что эти действия 
будут отличаться в зависимости от того, куда помещается символ. Если символ помещается в 
элемент строки, то необходимо знать только номер позиции, куда следует поместить символ. 
Если символ помещается в строку, то сначала, исходя их координаты X в строке, следует 
определить, в какой конкретно элемент строки (получить указатель на этот элемент) и в какую 
позицию в этом элементе необходимо поместить символ. Затем уже можно размещать символ в 
соответствующем элементе. Если же символ следует поместить в текст, то сначала по координате 
Y следует определить строку (получить указатель на эту строку), а затем уже выполнить все 
действия, связанные с размещением символа в строке. Таким образом, следует иметь три разные 
подпрограммы для трех различных типов. Т. к. все они выполняют в конце концов одно и то же 
действие - размещают символ в соответствующем месте, было бы заманчиво все их назвать одним 
именем. В языке Паскаль это делать запрещено - все подпрограммы должны иметь уникальные 
имена. Для ООП в Turbo Pascal сделано исключение - все эти подпрограммы могут иметь одно и 
то же имя.
В этой возможности - иметь несколько подпрограмм с одним и тем же именем - и заключается 
полиморфизм ООП. Вопрос, какая же конкретно подпрограмма будет использоваться в том или ином 
случае, определяется типом конкре¬тного объекта, использующего эту подпрограмму. Так, если 
объект типа tText, то будет использована подпрограмма, размещающая символ в тексте, если 
типа tLine, то подпрограмма, размещающая символ в строке, и т. д.

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

14.3. Виртуальные методы
В ряде случаев при описании тех или иных схожих объектов приходится писать методы, также 
схожие друг с другом и отличающиеся только отдельными деталями. Так, в рассматриваемом 
примере методы объектов типа tLIne (строка) и типа tText (текст), предназначенные для 
создания соответствующего элемента и включения его в структуру, выполняют одинаковый набор 
действий: выделяют место в динамической области памяти и размещают там соответствующий 
объект, а затем устанавливают соответствующие связи этого объекта в структуре текста. При 
этом установление связей в структуре вообще не зависит от типа создаваемого объекта (здесь 
необходимо установить связи с предыдущим и последующим элементом и в случае необходимости 
объявить новый элемент первым или последним в структуре - эти операции практически не 
зависят от типа эле¬мента). Однако, операция выделения памяти и размещения в ней 
соответствующего объекта существенно зависит от его типа. Таким образом, для объекта типа 
tLine этот метод можно было бы написать следующим образом: 

procedure tLine.NewEl(PointPredEl,PointNextEl:Pointer);
    {PointPredEl  - указатель на предыдущий элемент в  структуре,
     PointNextEl  - указатель на последующий элемент в  структуре}
var NewPoint:   Pointer;	{указатель на новый элемент}
begin
NewPoint := InitElem;
PutCormection(PointPredEl, NewPoint);
PutConnection(NewPoint, PointNextEl); 
end;

В этом методе первая строка (NewPoint := InltElem) - обращение к методу, выделяющему 
соответствующую область динамической памяти и размещающему там элемент строки, одновременно 
заполняя его строку информации Info пробелами. Две последующие сроки - обращение к методу 
PutConnection, устанавливающему связи между двумя элементами структуры (в данном случае - 
элементами строк). Эти два обращения устанавливают связи нового элемента строки с ее 
соседями: предыдущим и последующим элементами.
Если написать подобный метод для создания нового текста, то отличие будет в методе InitElem, 
который должен выделить место для объекта типа tLine (строка) , разместить его в этой 
области и создать начальный элемент строки.
Хотя написание двух методов NewEl для строки и всего текста эквивалентно, их нельзя 
объединить в один и поместить в объект-предок, т. к. обращение к InitElem у них представляет 
обращение к различным методам. Это неудобство можно обойти, объявив методы InitElem 
виртуальными. В этом случае, действительно, можно написать только один метод NewEl, а то, 
какой из методов InitElem будет выбираться в каждом конкретном случае, зависит от того, 
какой объект будет создаваться.

14.3.1. Объявление виртуальных методов
Чтобы объявить метод виртуальным, при описании типа-объекта после указания заголовка этого 
метода следует записать зарезервированное слово virtual;

type
tLine = object(tStructure)
function InitElem: Pointer; virtual;
 . . .
end;
tText = object(tStructure) 
 . . . 
function InitElem: Pointer, virtual;
 . . .
end;

Если метод где-то был объявлен виртуальным, то и все другие методы с тем же заголовком также 
должны быть объявлены виртуальными. Естественно, что у всех таких методов списки формальных 
параметров (и тип функции, если метод является подпрограммой-функцией) должны быть 
эквивалентными.
Метод, использующий виртуальные методы, должен быть размещен в объекте, доступном всем 
объектам, где этот метод должен применяться. Так, в нашем примере метод NewEl используется в 
объектах типов tLine и tText, которые являются непосредственными потомками абстрактного типа 
tStructure, поэтому естественно NewEl описать в типе tStructure, что и сделано в примере. 
Однако в этом случае тип tStructure должен также содержать виртуальный метод InitElem. Но в 
связи с тем, что объект типа tStructure является абстрактным, этот метод не должен явно 
выполнять никаких действий (в каком-то смысле является фиктивным) и имеет вид:

function tStructure.InitElem: Pointer;
begin
end;

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

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

constructor tLine.Init; 
begin
NewEl(nil, nil) 
end;

Т. к. использовать конструктор следует в программе как можно раньше (во всяком случае до 
первого использования виртуального метода), то целесообразно его использование объединить 
непосредственно с созданием конкретного объекта. С этой целью в Turbo Pascal стандартная 
процедура New дополнена вторым необязательным параметром - именем конструктора создаваемого 
объекта. Так, в секции инициализации модуля, выполняемой при запуске программы, при создании 
объекта типа tText (указатель на этот объект записывается в переменную PointText) 
одновременно происходит инициализация этого объекта конструктором Init:

New(PointText, Init);

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

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

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

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

uses Crt, Edit;
  {*** Новая обработка клавиши Dn ***} 
type
pInsertMyDn = ^tInsertMyDn;         (указатель на новый тип}
tInsertMyDn = object(tInsertDn)	{новый тип}
  constructor Init; 
  procedure Ins; virtual 
end;
var Dn1: pInsertMyDn;	{новая переменная}
constructor tInsertMyDn.Init;	{конструктор переменной нового типа} 
begin 
end;

procedure tInsertMyDn.Ins;	{новый метод}
begin
with PointText^ do
  if GetPointLine(GetY + 1) = nil then
                         {если нет следующей строки...}
    NewEl(GetLastElem, nil);  {- создать ее}
  tInsertDn.Ins	         {переместить курсор}
end;

{***  Основная программа ***} 
begin
  New(Dn1, Init);	{создание нового объекта}
  TextBackGround(Blue); TextColor(White) ; 
  ClrScr; GotoXY(l, 1); 
  with PointText^ do 
  repeat
    Ch := ReadKey; 
    if Ch = Chr(0) then 
      begin
        Ch := ReadKey; 
        case Ord(Ch) of
         72: Up^.PutNewSymb;
         80: Dnl^.PutNewSymb;    {обращение к новому методу}
         75: Left^.PutNewSymb; 
         77: Right^.PutNewSymb; 
        end
      end else 
        if Ch > Chr(31) then
          Symbol^.PutNewSymb 
            else 
              if Ch = Chr(Ent) then
                Enter^.PutNewSymb 
                   else 
                     if Ch = Chr(BackS) then
                       Backspace^.PutNewSymb 
  until Ch = Chr(Esc); 
Dispose(PointText, Done); 
TextBackGround(Black); ClrScr 
end.

Здесь введен новый тип tlnsertMyDn, являющийся наследником типа tlnsertDn. Его виртуальный 
модуль Ins сначала создает новую строку, если ее нет, а затем обращается к уже существующему 
методу tlnsertDn.Ins для перемещения курсора в новое положение. Изменена в тексте и строка, 
где происходит обращение к методу обработки клавиши Dn.

Заметим, что все изменения в этом случае можно сосредоточить в основной программе, не меняя 
и не перекомпилируя модуль.
Следует обратить внимание еще на один момент, связанный с методом tlnsertMyDu.Ins. Этот 
метод сначала выполняет какие-то свои операции, а затем вызывает аналогичный метод своего 
предка. Такая ситуация допустима и встречается довольно часто. Это также позволяет несколько 
сократить программу, не программируя повторно операции метода предка. Т. к. эта ситуация 
встречается довольно часто, в версии 7.0 допустимо при обращении к методу непосредственного 
предка не указывать его тип, а заменить его зарезервированным словом inherited. Так, в 
предыдущем примере рассматриваемый метод можно также записать следующим образом:

procedure tlnsertMyDn.Ins;	    {новый метод}
begin
with PointText^ do
if GetPointLine(GetY + l)=nil then  {если нет следующей строки...}
  NewEl(GetLastElem, nil);	    {-  создать  eft}
inherited  Ins	{переместить курсор}
end;

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

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

uses Crt, Edit;
{***  Обработка клавиш Home  и  End  ***} 
type
pInsertHome = ^tlnsertHome;	{указатель  на  новый  тип}
tInsertHome = object(tText)	{новый тип}
  constructor Init; 
  procedure  Ins; virtual 
end;
pInsertEnd =  ^tlnsertEnd;	{указатель на новый тип}
tInsertEnd = object(tText)	{новый  тип}
  constructor  Init; 
  procedure  Ins; virtual 
end;

var Home: pInsertHome;	{новая переменная}
    Endd: pInsertEnd;	{новая переменная}

constructor tInsertHome.Init;   {конструктор переменной нового типа}
begin 
end;

procedure tInsertHome.Ins;	{новый метод}
begin
with PointText^ do
PutX(l) 
end ;

constructor tInsertEnd.Init;	{конструктор переменной нового типа} 
begin 
end;

procedure tInsertEnd.Ins;	{новый метод}
var PointElLine: pElLine; 
    NomAbs: Word; 
    NomlnEl: Byte; 
begin
  with PointText^ do 
    begin
      CurrentPointLine^.LastNotBlank(PointElLine, NomAbs,
      NomlnEl); 
      PutX(NomAbs + 1) 
    end 
end;
{*** Основная программа  ***} 
begin
  New(Home, Init);	{создание нового объекта}
  New(Endd, Init);	{создание нового объекта}
  TextBackGround(Blue); TextColor(White);
  GotoXY(l, 1); 
  with PointText^ do 
    repeat
      Ch := ReadKey; 
      if Ch = Chr(0) then 
        begin
          Ch : = ReadKey; 
          case Ord(Ch) of
          72: Up^.PutNewSymb; 
          80: Dn^.PutNewSymb; 
          75: Left^.PutNewSymb; 
          77: Right^.PutNewSymb;
          71: Home^.PutNewSymb;      {обработка клавиши Home} 
          79: Endd^.PutNewSymb;      {обработка клавиши End} 
       end 
   end else 
   if Ch > Chr(31) then
     Symbol^.PutNewSymb else 
        if Ch = Chr(Ent) then
          Enter^.PutNewSymb else 
             if Ch = Chr(BackS) then
               BackSpace^.PutNewSymb 
   until Ch = Chr(Esc); 
  Dispose(PointText, Done); 
  TextBackGround(Black); ClrScr 
  end.
Rambler's Top100
Hosted by uCoz