Объективно-ориентированная технология программирования в языке Паскаль

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

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык русский
Дата добавления 17.02.2012
Размер файла 109,9 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Размещено на http://www.allbest.ru/

Министерство образования и науки Украины

Национальный технический университет Украины «КПИ»

Факультет Информатики и вычислительной техники

Курсовая робота:

OБЪЕKTHO-ОРИЕНТИРОВАННАЯ ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ В ЯЗЫКЕ ПАСКАЛЬ

Киев. 2011

1. Процедурный тип данных

Как уже упоминалось, версии языка, разрабатываемые фирмой Borland по отношению к Стандарту, содержат ряд расширений. Рассмотренные ранее расширения, включая средства модульного конструирования программ, тип указателя (pointer) и строковый тип (string) можно отнести к таким, которые в целом не изменяют основных концепций авторской версии языка.

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

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

Описание процедурного типа выполняется в разделе типов, т. е. соответствует принятому синтаксису языка, например:

type

Proc=procedure;

SwapProc=procedure(var X,Y : Integer);

StrProc=procedure(Str : String);

ReadProc = procedure (var S : String);

MathFunc=function(X : Real) : Real;

DeviceFunc=function(var F : Text) : Integer;

MaxFunc=function(A,B : Real; F: MathFunc) : Real;

Как видно из примеров, в описании процедурного типа используются зарезервированные слова procedure и function и приводится полное описание параметров, а в случае функции указываются и тип результата. Иными словами, синтаксис описания процедурного типа соответствует синтаксису обычного заголовка процедуры либо функции с той лишь разницей, что опускается их имя. Имена формальных параметров при этом так же играют играют чисто символическую роль и присутствуют в описании только для указания их количества и типа. Таким образом:

<процедурный тип> ::= procedure(<список формальных параметров>)

function(<список формальных параметров>) : <тип>

В приведенном выше примере при описании типа MaxFunc в качестве параметра используется процедурный тип MathFunc.

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

Определив процедурный тип, в программах можно использовать так называемые процедурные переменные:

var

P : SwapProc;

F: MathFunc;

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

type

SwapProc = procedure(var X, Y : Integer);

MathFunc = function(A,B : Real);

var

P: SwapProc;

F: MathFunc;

procedure Swap(var A,B : Integer); far;

var

Buf : integer;

begin

Buf := a;

A := B;

B := Buf;

end; {Swap}

function Tan(Angle : real); far;

begin

Tan := Sin(Angle) / Cos(Angle);

end; {Tan}

begin

P := Swap;

F := Tan;

P(I, J); {эквивалентно Swap(I, J)}

X :=F(X); {эквивалентно X :=Tan(x)}

Для обеспечения совместимости по присваиванию процедура либо функция в правой части от знака присваивания должна удовлетворять следующим требованиям:

Первое из этих ограничений связано с тем, что процедурный тип по представлению совместим с типом pointer. Переменным этого типа в версиях Borland Pascal соответствует адресное пространство в 4 байта длиной, задающее адрес объекта данных в виде <сегмент><смещение>. Для переменных процедурного типа, как уже упоминалось, этот адрес указывает на место в памяти, где находится выполнимый код соответствующей процедуры либо функции. По этой причине при компиляции программ должна быть использована, где это необходимо, директива ($f+) для формирования far-адресации <сегмент><смещение> при последующих вызовах объектов процедурного типа. То же самое можно сказать о вложенных в процедурный тип процедурах и функциях, которые при компиляции располагаются в сегменте памяти машины, зарезервированном под стек, и имеют near - адресацию. Более подробно с директивами, управляющими в этом случае процессом трансляции, можно ознакомиться с помощью фирменной документации для используемой версии системы программирования.

Чтобы иметь возможность присваивать переменным процедурного типа значения, представляющие собой стандартные функции, необходимо создавать специальную "оболочку". Например, стандартная функция sin(x), взятая в оболочку FSin совместима по присваиванию с описанным выше типом MathFunc:

function FSin(X : real): Real; far;

begin

FSin :=sin(X);

end;

tуре

($f+) {заменяет директиву far}

procedure MyRead (var S : String); {по типу совместима с ReadProc}

begin

Read (S);

end;

($f-)

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

Использование процедурных переменных, которым при инициализации или по другим причинам было присвоено значение nil, совместимое с любым процедурным типом, приводит к ошибке. Для исключения подобных ошибок обычно используется проверка вида if @P <> nil then P(I, J).

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

tуре

MyProc =procedure (X,Y : Byte);

Ref= ^Node;

Node =record

K.Y : Byte;

IsVisible : Boolean;

DrawProc : MyProc;

Next : Ref

end;

NodeArray = array [1..5] of ref;

var

N : Node;

NA : NodeArray;

Приведенное описание типов и переменных соответствует некоторой динамической структуре (линейному списку), ссылки на элементы которой “собраны” в массив NA. При таком описании возможны обращения вида:

N.DrawProc(1,5);

NA[4]^.DrawProc(6, 2);

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

2. Особенности объектно-ориентированной технологии

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

Первые идеи ООП были намечены и реализованы в языке SIMULA 67 (сокращенное от SIMUlation LAnguage - язык моделирования, 67 - год версии) и существенно развиты в объектно-ориентированных языках SmallTalk и C++. Благодаря последним, идеи ООП получили вполне конкретную реализацию. Однако сегодня еще рано говорить о создании определенного языкового стандарта для ООП. Это по-прежнему скорее конгломерат идей, наиболее полное изложение которых можно найти в [3]. Средства поддержки ООП органично вписались в легкую и доступную структуру языка ТцгЬо Pascal версии 5.5 и более поздних версий фирмы Borland и строились с использованием уже имеющегося опыта введения таких средств в язык C.

Как следует из самого названия, ключевым понятием ООП является объект (object). Объект часто называют классом. Это понятие характеризуется тремя основными свойствами:

Собственно, понятия инкапсуляции и полиморфизма были введены уже в языке SIMULA. Там же применялось наследование, хотя и не многократное, и поощрялось скрытие полей данных (privacy).

Впервые версию языка Паскаль, расширенную средствами ООП и названную Clascal, предложила компания Apple Computer. Следующим шагом этой компании была разработка версии Object Pascal с участием Н. Вирта. Результаты их разработки имеют близкое сходство с объектно-ориентированным Borland Pascal.

ООП средства Borland Pascal в свою очередь многим обязаны языку С++ (объектно-ориентированному надмножеству языка С). Конструктор и деструкртор - это понятия С++. Подобно этому языку Borland Pascal предлагает выбор между статичскими и виртуальными методами, а также скрытие (private) полей и методов. При этом заметно взаимное влияние: введение строгого контроля типов и ориентация на предопределенные структуры объектов делают язык С++ во многом концептуально похожим на Паскаль.

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

Наличие таких возможностей в большей степени присуще аппликативным языкам, предназначенным для, так называемого, функционального программирования (язык Норе []).

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

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

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

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

WriteLn(CharVar); {выводит значение символьного типа}

WriteLn(IntegerVar); {Выводит значение целого типа}

WriteLn(RealVar); {Выводит значение числа с плавающей запятой}.

К сожалению, в Стандарте нет средств, которые позволяли бы создавать свои, столь же гибкие процедуры (оператор case здесь мало поможет, поскольку селектором варианта в нем является значение метки, а не тип данных).

Однако средства поддержки этого свойства в ООП достаточно хорошо “отшлифованы” и в дальнейшем будут уточняться.

3. Конструкция object

Первое приближение

В первом приближении объект очень похож на запись, которая рассматривется как оболочка для объединения нескольких взаимосвязанных элементов данных под одним именем. Так, например можно поместить в одну оболочку координаты X и Y, задающие позицию на экране видеомонитора, представив их записью с именем Location (место):

Location = record

X, Y : Integer;

end;

Пусть теперь по условию задачи требуется показать световую точку на экране в позиции, заданной записью Location. Для этого тип Location обеспечивает достаточную гибкость, которая позволяет рассматривать координаты X и Y раздельно и в то же время воспринимать запись как единое целое, т. е. как позицию на экране.

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

Location = record

X, Y : Integer;

Visible : Boolean;

end;

либо, что предпочтительней, определить новый тип Point (точка):

Point = record

Position : Location;

Visible : Boolean;

end;

Предпочтение в этом случае обусловлено тем, что отражает следующий факт: точка - это позиция на экране (Location), которая светится. В таком определении точки “наследуется” местонахождение для этой точки. Однако тип записи по определению не может наследовать в полном смысле этого слова. При описании переменной типа Point вида var MyPoint : Point обращение к полям X и Y должно выполняться с помощью операторов MyPoint .Location.X :=0 и MyPoint .Location.Y :=0, т. е. требуется явное указание имени переменной-записи, которой они принадлежат (оператор with здесь принципиально ничего не изменит). Таким образом, являясь принадлежностью записи Location, эти поля не могут непосредственно рассматриваться как поля записи Point, хотя в действительности они там есть.

Использование типа object при описании таких данных существенно изменяет ситуацию, вводя наследование как особый вид отношений. Так как все точки должны иметь место на экране, можно говорить о том, что тип Point является производным типом от типа Location. Иначе говоря, тип Point непосредственно наследует все поля, принадлежащие типу Location, и дополнительно имеет то новое, что делает Point отдельным типом. При этом наследник является производным типом ("потомком" - immediate descendant, child), а тип, от которого производится наследование, - прародительским типом (“предком” - immediate ancestor). Согласно синтаксису этот тип определяется новым служебным (зарезервированным) словом object, после которого в круглых скобках при определении потомков явно указыватся их непосредственный предок.

Для рассмотренного выше примера два взаимосвязанных типа объектов могут быть определены следующим образом:

Location = object

X, Y : Integer;

end;

Point = object (Location)

Visible : Boolean;

end;

Здесь следует обратить внимание на обязательную точку с запятой перед end, отсутствие явного упоминания о наличии полей X и Y при описании типа Point и использование круглых скобок доля обозначения наследования. Несмотря на то, что поля X и Y типа Location явно не описаны в типе Point, этот тип их имеет, получив поля посредством наследования. Поэтому можно говорить о величине поля X типа Point точно так же, как о величине поля X типа Location.

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

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

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

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

Экземпляры типов объектов описываются подобно тому, как описываются любые переменные: либо как статические переменные, либо как указатели на объекты, распределяемые в динамической области памяти (Heap):

type

Location = object

X, Y : Integer;

end;

Point = object (Location)

Visible : Boolean;

end;

PointPtr = ^Point;

var

StatPoint : Point; {статический объект, который можно использовать}

DynPoint : PointPtr; { должен быть инициализирован перед

использованием с помощью процедуры new}

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

StatPoint.Visible := False;

StatPoint.X := 0;

StatPoint.Y := 0;

или

with StatPoint do

begin

X := 0;

Y := 0;

end;

Здесь следует помнить, что унаследованные поля объекта Location в экземпляре StatPoint доступны.

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

Методы

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

<объект> ::= obect

<список полей >;<список методов>;

end

obect (<имя предка>)

<список полей >;<список методов>;

end

<список методов> ::= <метод >;

<список методов>;

<метод>;

< метод> ::= <заголовок процедуры>

<заголовок функции>

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

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

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

<заголовок метода> ::= <имя объекта >.< заголовок процедуры >;

<имя объекта >. < заголовок функции >

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

Так, например, позиции на экране видеомонитора, представляемой объектом, может соответствовать описание типа:

type

Locatiion = object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

procedure Location.Init(InitX, InitY : Integer);

begin

X := InitX;

Y := InitY;

end;

function Location.GetX : Integer;

begin

GetX := X;

end;

function Location.GetY : Integer;

begin

GetY := Y;

end;

Для инициализации (установки в заданную позицию) экземпляра типа Location в этом случае достаточно вызвать метод таким же способом, как поле записи, чем метод, впрочем, по представлению и является:

var

MyLocation : Location;

begin

MyLocation.Init(17, 42); {инициализация позиции с координатами X=17,

. . . Y=42}

В связи с примером можно подчеркнуть, что Borland Pascal допускает применение к полям данных объекта всех операций, соответствующих типу поля, но выполнение таких операций крайне нежелательно. Например, в языке SmallTalk действия над полями, которые не предусмотрены методами, вообще запрещены, или, иными словами, свойство инкапсуляции определено гораздо строже, чем в Borland Pascal.

Плохим примером в этом смысле будет замена вызова метода MyLocation.Init(0,0) операторами

MyLocation.X := 1;

MyLocation.Y := 1;

результат выполнения которых будет тот же - инициализация левой верхней позиции экрана. Такая замена допустима, но крайне нежелательна, что является одним из основных требований ООП.

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

Иными словами для операций со значениями одного или нескольких полей объекта следует вызывать метод, принадлежащий этому объекту. Метод либо устанавливает величину требуемого поля, либо возвращает ее, определяя что нужно “сделать” с объектом, а как это делается - не важно и “скрыто” в описании метода.

Такая методология описания действий обеспечивает явные преимущества технологии ООП:

Возможность “смысловой” трактовки действий (что нужно “сделать”), свойственной аппликативным языкам.

2 Полную степень свободы в выборе требуемого набора действий над полями объекта. При описании объектов не следует воздерживаться от формулировки “лишних” методов, которые могут и не использоваться в программе, использующей данный тип объекта. Лишние методы не приведут к избыточности, как в смысле эффективности кода рабочей программы, так и в его размерах (размерах файлов с расширением .EXE ), поскольку описание этих методов увеличивает объект только в исходной форме, а компоновщик транслятора Borland Pascal игнорирует все коды методов, которые не вызываются в программе. Следовательно, в рабочей программе их просто не будет.

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

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

Пример объекта Location в этом смысле мало иллюстративен. Перечисленные особенности начинают проявляться при формировании дерева порождений.

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

Точка - это видимая позиция на экране. Поэтому соответствующий ей объект Point можно описать, используя имеющийся объект Location, дополнив его полем признака того, видима точка на экране или нет (Visible : boolean), а также методами, позволяющими показать точку (procedure Show), погасить ее (procedure Hide), переместить на новую позицию (procedure MoveTo), вернуть значение признака (function IsVisible) и заново определить метод, инициализирующий точку. С учетом наследования для этого не требуется повторять в описании те поля, которые определяют позицию точки и методы объекта Location, используемые при описании методов объекта Point. Предок для наследования при описании типа, согласно синтаксису, явно указывается в круглых скобках после зарезервированного слова object..

Таким образом, определению объекта Point = object(Location) соответствует описание типа:

Point = object(Location)

Visible : Boolean;

procedure Init(InitX, InitY : Integer);

procedure Show;

procedure Hide;

function IsVisible : Boolean;

procedure MoveTo(NewX, NewY : Integer);

end;

procedure Point.Init(InitX, InitY : Integer);

begin

Location.Init(InitX,InitY); {унаследованный метод}

Visible := False

end;

procedure Point.Show;

begin

Visible := True;

PutPixel(X, Y, GetColor)

end;

procedure Point.Hide;

begin

Visible := false;

PutPixel(X, Y, GetBkColor)

end;

function Point.IsVisible : Boolean;

begin

IsVisible := Visible

end;

function Point.MoveTo(NewX, NewY : Integer);

begin

Hide;

X := NewX;

Y := NewY;

Show;

end

end;

При определении методов объекта Point использованы стандартная процедура модуля Graph (PutPixel), а для его инициализации - метод Point.Init, предписывающий при инициализации точки делать ее невидимой.

Далее, отметив, что круг это всего лишь “толстая точка”, толщина которой задается радиусом, можно определить очередной объект в иерархии описания геометрических фигур - объект Circle, который будет наследовать Point и иметь дополнительное поле Radius и расширенный список методов. На этом же уровне иерархии можно определить квадрат, прямоугольник, кривую и другие фигуры. Затем, например, кольцо как два круга, один из которых вложен в другой и всегда невидим, и т. п. Дерево порождения для рассмотренных примеров иллюстрируется рисунком (Рис.8.1).

Учитывая, что синтаксис описания объектного типа напоминает конструкцию интерфейсного раздела модуля, а непосредственная формулировка методов выполняется вне описания типа подобно тому, как это делается в секции реализации, обычно рекомендуется оформлять объекты в виде модулей. Для определения объекта в модуле не требуется специальный синтаксис и нет дополнительных затруднений (в смысле наследования) при конструировании коллекций, поскольку тип объекта, который определен в интерфейсном разделе модуля, может иметь производные типы, определенные в разделе реализации этого модуля. В том случае, когда модуль В использует модуль А, в модуле В также можно определять производные типы от любого типа объекта, описанного в модуле А (“экспортируемого” модулем А), если они не защищены спецификатором private. Этот спецификатор, допустим в версиях, начиная с шестой, и действует на все поля и методы, которые следуют за ним в описании типа объекта. Скрытые с его помощью поля и методы доступны только внутри самого объекта, в котором они описаны, или внутри других подпрограмм либо методов, определенных в секции реализации того же модуля.

Типы объектов Locatiion и Point, а также тела их методов, в модуле определяются следующим образом:

unit Points;

interface

uses Graph

type

Locatiion = object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

Point = object(Location)

Visible : Boolean;

procedure Init(InitX, InitY : Integer);

procedure Show;

procedure Hide;

function IsVisible : Boolean;

procedure MoveTo(NewX, NewY : Integer);

end;

procedure Point.Init(InitX, InitY : Integer);

begin

Location.Init(InitX,InitY);

Visible := False

end;

procedure Point.Show;

begin

Visible := True;

PutPixel(X, Y, GetColor)

end;

implementation

{ Реализация методов модуля Location }

procedure Location.Init(InitX, InitY : Integer);

begin

X := InitX;

Y := InitY

end;

function Location.GetX : Integer;

begin

GetX := X

end;

function Location.GetY : Integer;

begin

GetY := Y

end;

{ Реализация методов модуля Point }

procedure Point.Init(InitX, InitY : Integer);

begin

Location.Init(InitX,InitY); {унаследованный метод}

Visible := False

end;

procedure Point.Show;

begin

Visible := True;

PutPixel(X, Y, GetColor)

end;

procedure Point.Hide;

begin

Visible := False;

PutPixel(X, Y, GetBkColor)

end;

function Point.IsVisible : Boolean;

begin

IsVisible := Visible

end;

function Point.MoveTo(NewX, NewY : Integer);

begin

Hide;

X := NewX;

Y := NewY;

Show;

end

end;

Чтобы использовать объекты и правила, определенные в модуле Points, необходимо “подключить” этот модуль с помощью инструкции uses к своей программе и определить экземпляр типа Point в разделе переменных:

program MakePoints;

uses Graph, Points;

var

APoint : Point;

begin

APoint.Init(151, 82); { инициализация X, Y в 151,82 }

APoint.Show; { точка (APoint) включается }

APoint.MoveTo(163, 101); { точка перемещается в 163,101 }

APoint.Hide; { Точка выключается }

или с использованием with, что применительно к методам допускается синтаксисом, поскольку это те же поля записи, но поля процедурного типа:

with APoint do

begin

Init(151, 82);

Show;

MoveTo(163, 101);

Hide;

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

В качестве замечания можно еще раз отметить, что корнем дерева, как и любой генеалогической схемы в естествознании, должен служить объект, определяющий некоторое ключевое свойство, общее для всех объектов коллекции. В рассматриваемых примерах таким ключевым объектом является объект Location. Все другие объекты коллекции наследуют его ключевые признаки. Такой объект, вообще говоря, может быть абстракцией, к которой в программе не будет непосредственных обращений (собственно, Location и Point являются именно абстрактными объектами (abstract object type). На каждом новом уровне иерархии порождения коллекции при описании потомков добавляется какой-либо новый признак, позволяющий детализировать объекты этого уровня и отличать их от предшественников. Следовательно, при формировании коллекции объектов в ООП необходимо позаботиться о том, чтобы корневой объект схемы имел только те свойства, которые являются общими для всей коллекции. Иначе вносимые изменения могут затрагивать все дерево порождения, что нежелательно. Если сопоставить сказанное выше с возможностями модульной организации программ, то становится очевидным следующее: при определении какой-либо коллекции в виде отдельно компилируемого модуля для нового порождения потомка достаточно подключить в этот модуль соответствующий .TPU-файл (или .TPW-файл для оконных версий), уменьшив тем самым затраты на введение объекта с новым качеством.

Контекст правила и параметр Self

При формулировке методов для объектов, рассматриваемых в примерах, при обращении к их полям не использовалась конструкция with object do, т.е. предполагалось, что поля данных являются свободно доступными для методов этого объекта. Таким образом, будучи разделенными в исходной программе, тела методов и поля данных объекта фактически совместно используют один и тот же контекст (область действия, видимости или доступности). Поэтому, например, метод объекта Location может содержать оператор GetX :=Y без какого-либо спецификатора для Y.

Возможность такой формулировки метода обусловлена тем, что поле Y принадлежит объекту, который вызвал этот метод и при вызове метода по умолчанию используется неявный оператор with MySelf do Method. Этот неявный оператор вседа сопровождается передачей методу “невидимого” параметра, который имеет зарезервированное имя Self, и фактически является указателем на соответствующий экземпляр объекта.

Методу GetY, который принадлежит объекту Location, в этом смысле может быть поставлен в соответвие синтаксически не вполне корректный (Self не может быть именем параметра), но поясняющий сказанное выше фрагмент кода:

function Location.GetY(var Self : Location) : Integer;

begin

GetY := Self.Y

end;

В реальных программах указатель Self, как правило, не используется, поскольку компиляторами Borland Pascal он практически всегда учитывается автоматически. Но знать о его существовании полезно, поскольку в некоторых немногочисленных ситуациях приходится "входить" внутрь метода и использовать параметр Self явно. Такая ситуация может возникать, когда имена полей данных конфликтуют в пределах методов, но избежать ее достаточно легко, используя уникальные имена для этих полей.

Явное использование параметра Self всего лишь допустимо, но при этом, подобно непосредственному обращению к полям объекта, минуя метод, противоречит принципам ООП. Однако это замечание в системах программирования Borland Pascal не распространяется на методы, реализованные как ассемблерные вставки. За более подробной информацией о вызовах таких методов необходимо обращаться к разделу "Условия вызова правил" в фирменной документации.

Совместимость типов объектов

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

Во всех случаях совместимость объектных типов простирается только от потомка к прародителю. Иными словами, производные типы могут использоваться вместо прародительских типов, но не наоборот, поскольку производные типы, благодаря наследованию, содержат поля прародительского типа. Следовательно, производный тип всегда их “заполнит”. В противном случае некоторые из полей потомка останутся неопределенными после присваивания, (их просто нет у прародителя), что опасно с точки зрения тупиковых для транслятора ситуаций, и, следовательно, недопустимо. Так, при описаниях в разделах типов и переменных:

type

LocationPtr = ^Location;

PointPtr = ^Point;

CirclePtr = ^Circle;

var

ALocation : Location;

APoint : Point;

ACircle : Circle;

PLocation : LocationPtr;

PPoint : PointPtr;

PCircle : CirclePtr;

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

ALocation := APoint;

APoint := ACircle;

ALocation := ACircle;

Обратные присваивания, естественно, не допустимы.

В результате выполнения первого оператора присваивания в ALocation будут скопированы только поля X и Y, а поле Visible будет игнорироваться.

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

PPoint := PCircle;

PLocation := PPoint;

PLocation := PCircle;

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

procedure DragIt(Target : Point):

фактические параметры могут быть параметрами типа Point или Circle.

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

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

procedure Figure.Add(NewFigure : PointPtr);

в качестве ее фактического параметра нельзя использовать только LocationPtr. Последнее, как будет показано ниже, лежит в основе позднего связывания.

язык методология модульность абстракция

4. Наследование правил и полиморфизм

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

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

Пояснить это можно на примере объекта Circle:

Circle = object(Point)

Radius : Integer;

procedure Init(InitX, InitY : Integer; InitRadius : Integer);

procedure Show;

procedure Hide;

procedure Expand(ExpandBy : Integer);

procedure MoveTo(NewX, NewY : Integer);

procedure Contract(ContractBy : Integer);

end;

procedure Circle.Init(InitX, InitY : Integer; InitRadius : Integer);

begin

Point.Init(InitX, InitY);

Radius := InitRadius;

end;

procedure Circle.Show;

begin

Visible := True;

Graph.Circle(X, Y, Radius);

end;

procedure Circle.Hide;

var

TempColor : Word;

begin

TempColor := Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible := False; Graph.Circle(X, Y, Radius);

Graph.SetColor(TempColor)

end;

procedure Circle.Expand(ExpandBy : Integer);

begin

Hide;

Radius := Radius + ExpandBy;

if Radius < 0 then Radius := 0;

Show

end;

procedure Circle.Contract(ContractBy : Integer);

begin

Expand(-ContractBy)

end;

procedure Circle.MoveTo(NewX, NewY : Integer);

begin

Hide;

X := NewX;

Y := NewY;

Show

end;

Этот объект имеет только одно "собственное" поле Radius, и все поля данных, которые он унаследовал от объекта Point, т. е. поля X, Y, и Visible. Правила GetX и GetY наследуются от объекта Location без изменений.

Наличие нового поля Radius потребует изменений в методе Init для инициализации не только унаследованных полей, но и поля Radius. Вместо непосредственнго присваивания значений унаследованным полям X, Y и Visible, в методе Circle.Init используется унаследованный метод Point.Init. После вызова Point.Init, в теле Circle.Init выполняется присваивание полю Radius значения фактического параметра, переданного на месте формального параметра InitRadius.

Вместо того, чтобы “показывать” и “прятать” круг в целом, а не точка за точкой, используется процедура модуля Graph. В этом случае для объекта Cirсle понадобятся новые методы Show и Hide, которые заменяют аналогичные для объекта Point..

Вызов Graph.Circle в методе Circle.Show указывает на подпрограмму Circle в модуле Graph, а не на тип объекта Circle.

В определении объекта Circle также заменен метод MoveTo объекта Point, о чем свидетельствует спецификатор (префикс) объекта Circle перед идентификатором MoveTo. При этом замененный метод не использует поле Radius и в нем отсутствует вызов Graph.Circle для рисования круга, т. е. Circle.MoveTo полностью повторяет формулировку Point.MoveTo, что может вызвать некоторую растерянность и объясняется следующим.

Все методы, рассматриваемые до сих пор в связи с типами объектов Location, Point и Circle, являлись статическими. Термин "статические" (static) здесь используется применительно к методам, которые не являются "виртуальными" - virtual).

Особенности статических методов таковы: пока копия метода MoveTo не будет помещена в контекст объекта Сircle для замены метода MoveTo объекта Point, метод не будет выполнять нужные действия, если он вызывается из объекта типа Circle. При вызове для объекта Circle метода MoveTo объекта Point на экране будет передвигаться не круг, а точка. Только если объект Circle вызывает копию MoveTo, определенную как Circle.MoveTo круг будет “показан” и “спрятан” вложенными вызовами Show и Hide.

Последнее определяется способом компиляции статических методов. При компиляции методов объекта Point в коде метода Point.MoveTo используются вызовы Point.Show и Point.Hide в соответствии с описанием метода:

procedure Point.MoveTo(NewX, NewY : integer);

begin

Hide; {вызывает Point.Hide}

X := NewX;

Y := NewY;

Show; {вызывает Point.Show}

end;

Таким образом вызов Point.MoveTo предполагает вызовы Point.Show и Point.Hide. Если бы объект Circle наследовал метод объекта Point, последний исполнялся бы так, как был ранее откомпилирован. Вместо Circle.Show и Circle.Hide выполнялись бы Point.Show и Point.Hide и перемещалась точка, а не круг. При переопределении метода в его код включаются вызовы процедур Show и Hide, определенные в его контексте, то есть Circle.Show и Circle.Hide.

Логика “работы” компилятора при вызове статических методов такова: когда вызывается метод, компилятор сначала ищет метод с таким именем, определенный в пределах типа объекта. Поскольку методы с именами Init, Show, Hide, и MoveTo переопределены в объекте Circle, компилятор использует вызов одного из перечисленных методов. Если метод с таким именем не найден в пределах типа объекта, то компилятор переходит к типу непосредственного прародителя, и снова ищет указанный метод. При положительном исходе вызов метода потомка заменяется вызовом метода прародителя. В противном случае продолжается поиск вверх по дереву порождения. Отсутствие метода в корне дерева вызывает сообщение об ошибке, свидетельствующее о том, что такое правило в данной коллекции не определено.

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

5. Виртуальные методы и полиморфизм

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

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

Так, например, тип объекта в рассматриваемой иерархии геометрических фигур представляет отдельный тип фигуры на экране: точку или круг. Для определения объектов, соответствующих другим фигурам (линии, квадрату, дуге и т. п.), потребуется описать методы для каждого объекта, которые “показывают” или “перемещают” эти объекты на экране.

Общим для всех типов фигур является то, что их можно “показывать” или “перемещать”. В этом смысле все они одинаковы. Отличие же этих типов друг от друга заключается в способе, которым каждый объект рисуется на экране. Точка рисуется подпрограммой графического построения точки, для которой нужно только местонахождение X,Y. Для рисования круга потребуется отдельная графическая подпрограмма, для которой будут необходимы не только координаты, но и радиус. Дуге, к примеру, нужен начальный угол и конечный угол, а также более сложная процедура рисования. При позднем связывании имя соответствующего метода (Show или MoveTo) используется для показа в буквальном смысле многих форм или, иначе говоря, является полиморфным. Виртуальность, как понятие, в данном случае говорит о том, что при вызове метода определяется само действие, а не способ, которым оно реализуется.

В этом случае может оказаться полезной такая аналогия: разница между вызовом статического правила и вызовом виртуального правила - это разница между решением, принимаемым на стадии планирования, и решением, принятие которого откладывается на неопределенное время.

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

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


Подобные документы

  • Логические конструкции в системе программирования Паскаль. Команды языка программирования, использование функций, процедур. Постановка и решение задач механики в среде системы Паскаль. Задачи статики, кинематики, динамики решаемые с помощью языка Паскаль.

    курсовая работа [290,9 K], добавлен 05.12.2008

  • Основные сведения о системе программирования Турбо Паскаль. Структура программы на Паскале и ее компоненты. Особенности и элементы языка Турбо Паскаль. Порядок выполнения операций в арифметическом выражении, стандартные функции и оператор присваивания.

    лекция [55,7 K], добавлен 21.05.2009

  • Стандартизированный процедурный язык программирования. Создание системного программного обеспечения и прикладных программ. Особенности языка Си, его основные недостатки. Передача параметров в функцию по значению. Стандартная библиотека языка Си.

    презентация [396,3 K], добавлен 12.11.2012

  • Цели и задачи дисциплины "Технология программирования". Программные средства ПК. Состав системы программирования и элементы языка. Введение в систему программирования и операторы языка Си. Организация работы с файлами. Особенности программирования на С++.

    методичка [126,3 K], добавлен 07.12.2011

  • Особенности программирования на языке Паскаль в среде Турбо Паскаль. Линейные алгоритмы, процедуры и функции. Структура данных: массивы, строки, записи. Модульное программирование, прямая и косвенная рекурсия. Бинарный поиск, организация списков.

    отчет по практике [913,8 K], добавлен 21.07.2012

  • История и основы структурного программирования в среде Turbo Pascal. Работа с различными типами данных. Операторы языка. Работа с символьными и строковыми переменами, одномерным, двумерным массивами. Классификация компьютерных игр. Игры на языке Паскаль.

    курсовая работа [28,8 K], добавлен 06.05.2014

  • Сущность понятия "тип данных". Объектно-ориентированный стиль программирования. Простые типы данных в языке Паскаль: порядковые, вещественные, дата-время. Булевский (логический) тип. Синтаксис определения ограниченного типа. Регулярные типы (массивы).

    реферат [24,1 K], добавлен 01.12.2009

  • Совместимость и преобразование типов данных. Создание информационно-поисковой системы на языке программирования Паскаль. Описание интерфейса, каждого блока программы "Картотека больных". Рассмотрение результатов работы программы, сортирования данных.

    курсовая работа [368,9 K], добавлен 18.05.2015

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

    курсовая работа [1,5 M], добавлен 04.12.2021

  • Использование объектно-ориентированной методологии при программировании математических процессов. Среда языка программирования Delphi для решения математических задач. Объектно-ориентированные, декларативные и императивные языки программирования.

    дипломная работа [1,8 M], добавлен 14.09.2011

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.