http://sulfurzona.ru/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Wing-Thunder Game (fly simulator)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
Скачать игру Крыло-Гром (Wing-Thunder) бесплатно
 
 

Паскаль для новичков (часть 33)

 

Средства объектно-ориентированного программирования

 
Автор: Владислав Демьянишин
 
Паскаль для новичковОбъектно-ориентированное программирование (ООП) основывается на этаких «трех китах» – трех важнейших принципах, коими являются инкапсуляция, наследование и полиморфизм.
  
Инкапсуляция – это объединение данных и подпрограмм обработки этих данных в единую программную структуру (объект). В ООП данные называются полями (fields) объекта, а подпрограммы – объектными методами (methods).
  
Инкапсуляция обеспечивает в широкой степени изоляцию объекта от внешнего окружения и существенно повышает надежность разрабатываемых программ, поскольку локализованные в объекте подпрограммы обмениваются с программой сравнительно небольшими объемами данных, причем количество и тип этих данных обычно тщательно контролируются. И как следствие, замена или модификация методов и данных, инкапсулированных в объект, как правило, не влечет за собой плохо прослеживаемых последствий для программы в целом (и в целях повышения защищенности программ в ООП почти не используются глобальные переменные).
 
Еще одним немаловажным достоинством инкапсуляции является легкость и простота обмена объектами, переноса их из одной программы в другую. В какой-то мере ООП “побуждает” программистов к разработке библиотек объектов.
  
Объект – это структура данных, по виду напоминающая комбинированный тип (запись), но может содержать не только поля, но и методы (процедуры и функции).
  
Описание типа объекта выглядит почти так же как и описание составного типа, однако вместо служебного слова record используется служебное слово object:
 
type
       TLine = object
        Left, Top, Right, Bottom : integer;
        Enabled : boolean;
       end;
 
где Left, Top, Right, Bottom, Enabled – поля объекта. Такой объект может хранить, например, координаты Left, Top и Right, Bottom вершины отрезка на экране, и статус видимости Enabled на экране. Это пример простейшего объекта, и пока преимуществ над обычными записями не видно. В отличие от записи, объект может иметь подпрограммы, управляющие его поведением в соответствии со значениями его полей. Тогда можно объявить метод Create( Left, Top, Right, Bottom : integer ) для начальной инициализации объекта. При этом имена входных параметров подпрограммы Create совпадут с именами полей объекта, что может привести к ошибке компиляции “Error 4: Duplicate identifier (Left)” (продублирован идентификатор Left). Поэтому логичнее будет все имена полей объекта озаглавливать буквой F (от слова Field–поле) тогда объявление объекта будет выглядеть по-другому:
 
type
       TLine = object
        FLeft, FTop, FRight, FBottom : integer;
        FColor : word;
        FEnabled : boolean;
        procedure Create( ALeft, ATop, ARight, ABottom : integer );
        procedure SetColor( AColor : word );
        procedure Enable;
        procedure Disable;
        procedure Move( ALeft, ATop : integer );
        function Left : integer;
        function Top : integer;
        function Right : integer;
        function Bottom : integer;
        function Enabled : boolean;
       end;
 
В объявлении типа объекта, как и в данном объявлении, заголовки методов указываются после объявления всех полей объекта. И раз уж в объявлении указаны заголовки методов объекта, то далее в листинге следует обязательно указать полное описание методов, при этом имя метода должно состоять из имени типа объекта и собственного имени метода, разделенных точкой (по аналогии с комбинированными типами):
 
procedure TLine.Create( ALeft, ATop, ARight, ABottom : integer );
begin
FLeft := ALeft;
FTop := ATop;
FRight := ARight;
FBottom := ABottom;
FColor := White;
FEnabled := false;
end;
 
procedure TLine.SetColor( AColor : word );
begin
FColor := AColor;
end;
 
procedure TLine.Enable;
begin
if FEnabled then exit;
FEnabled := true;
Graph.SetColor( FColor );
Line( FLeft, FTop, FRight, FBottom );
end;
 
procedure TLine.Disable;
begin
if not FEnabled then exit;
FEnabled := false;
Graph.SetColor( GetBkColor );
Line( FLeft, FTop, FRight, FBottom );
end;
 
procedure TLine.Move( ALeft, ATop : integer );
var dx, dy : integer;
begin
Disable;
dx := FRight – FLeft;
dy := FBottom – FTop;
FLeft := ALeft;
FTop := ATop;
FRight := FLeft + dx;
FBottom := FTop + dy;
Enable;
end;
 
function TLine.Left : integer;
begin
Left := FLeft;
end;
 
 
function TLine.Enabled : boolean;
begin
Enabled := FEnabled;
end;
 
Причем в каждом методе объекта считаются известными (видимыми) все поля этого объекта. Такое слияние информации об объекте (полей) и методов его поведения (функционирования, деятельности) в едином объектном типе позволяет создавать замкнутую, законченную среду для виртуализации прототипов практически любой сложности, начиная от простой линии на экране дисплея и обычного электронного регулировщика дорожного движения (светофора), до сложнейших молекулярных и галактических систем.
  
То, что было проделано выше, называется не самим объектом, а лишь объектным типом. Чтобы стало возможным применять свойства объекта, необходимо создать экземпляр объекта, объявив переменную объектного типа:
 
var
      Line : TLine;
 
после чего можно обратиться (вызвать, выполнить) любой метод объекта, указав составное имя метода, составленное по аналогии с селектором записи, но необходимо помнить, что слева от точки всегда должна находиться переменная объектного типа, а справа –
имя метода или поля:
 
begin
Line.Create(100,100,200,200);
Line.Enable;
Line.Move(200,100);
end.
 
Если таких обращений к элементам объекта много, то как и для записей, для объектов допустимо применение оператора присоединения, тогда данный фрагмент кода будет выглядеть так:
 
begin
with Line do begin
        Create(100,100,200,200);
        Enable;
        Move(200,100);
        end;
end.
 
Следует заметить, что, не смотря на то, что к полям объекта можно обращаться так же непосредственно, как и к его методам, это желательно делать только внутри его методов, так как обращение к полю объекта напрямую из программы является отступлением от объектно-ориентированного стиля программирования. Поэтому следует осуществлять все манипуляции с полями объекта только через его методы. Следующий код эквивалентен вызову метода Line.Create(100,100,200,200), и не вызовет ошибки, но в дальнейшем лишит программу гибкости, так как реализация метода Create может претерпеть изменения, дополнения, а данный код будет работать все так же по старинке.
 
begin
with Line do begin
         FLeft := 100;
         FTop := 100;
         FRight := 200;
         FBottom := 200;
         FColor := White;
         FEnabled := false;
         end;
end.
 
Поэтому логичнее вызывать метод, вместо изменения значений полей объекта непосредственно из программы. К тому же вызов метода выглядит короче, что повысит читабельность программы.
  
Используя идентификатор объектного типа в var-блоке можно объявить сколь угодно экземпляров объекта.
  
Из всех подпрограмм, описанных в листинге программы, и используемых ею модулях, в результативный код войдет код только тех, к которым есть обращения (вызовы). Аналогичная ситуация и с методами объектов, то есть в результативный код войдет код только вызываемых методов объекта, что позволит создавать эффективный код программ.
  
В дальнейшем для тестирования объектов будем использовать следующую программу:
 
Uses profiler, Graph;
 
var GraphDriver, GraphMode, ErrorCode : Integer;
 
type
       TLine = object
       …
 
procedure InitGrp;
begin
{ драйвер и режимы по умолчанию }
GraphDriver := Detect;
{ драйвер egavga.bgi в текущей папке }
InitGraph(GraphDriver, GraphMode, 'egavga.bgi');
ErrorCode := GraphResult;
{ обработка ошибки }
if ErrorCode <> grOK then begin
   Writeln('Graphics error: ', GraphErrorMsg(ErrorCode));
   CloseGraph;
   Halt(1);
   end;
end;
 
var
    Line : TLine;
    j : integer;
 
Begin
InitGrp;
Line.Create(100,100,200,200);
Line.Enable;
readln;
for j:=1 to 100 do begin
      Line.Move(100+j,100);
      {временная задержка 25 мс}
      NewDelay(25);
      end;
readln;
CloseGraph;
End.
 
где для вывода на графический экран будем использовать возможности модуля Graph из пакета Turbo Pascal.
  
При запуске программы на экране появится белая линия, а после нажатия на клавишу ENTER линия будет плавно двигаться вправо. Выполнение программы завершается еще одним нажатием той же клавиши.
 

Наследование и переопределение

 
Итак, ранее мы сконструировали объект TLine, управляющий линией на экране дисплея и это получилось весьма недурственно. Но что делать, если нам понадобится создать объект для управления прямоугольником, такой себе рамкой на экране. Что же нам опять составлять новый объектный тип и опять переписывать кучу кода? Нет уж, давайте воспользуемся наследованием. Что это такое? О, это такая фишка, всем фишкам фишка ;o) Ну а если серьезно, то в Turbo Pascal имеется возможность объявлять новые объектные типы как потомки уже существующего объектного типа, что позволяет потомкам унаследовать все поля и методы родителя (предка), а так же дополнять объекты новыми полями и заменять (переопределять, перекрывать) методы родителя или дополнять их. Вот это собственно и есть наследование.
  
Чтобы описать объект-потомок достаточно после служебного слова object указать в круглых скобках идентификатор типа объекта-родителя:
 
type
       TFrame = object (TLine)
         FFilled : boolean;
        function Filled : boolean;
       end;
 
что будет означать, что все поля и методы родителя TLine присутствуют неявно, и стало быть присовокупляются к потомку –
родная кровь все таки ;o) При этом, в новый объект можно ввести дополнительные поля и методы, если в этом есть необходимость. Так в потомок TFrame было введено поле FFilled, управляющее заливкой рамки, что позволит получать на экране либо не закрашенный прямоугольник-рамку, либо закрашенный прямоугольник аля “Черный квадрат” Малевича Казимира Севериновича ;o)
  
В свою очередь, от типа TFrame можно образовать, так сказать, породить потомок, например, TWindow, ведь свойства рамки и окна очень схожи. Тогда объект TFrame будет предком по отношению к TWindow, а TWindow будет прямым потомком объекта TFrame, и косвенным потомком объекта TLine, и стало быть унаследует все поля и методы, которые были описаны в его предках. Такое объектное генеалогическое древо своими ветвями может подниматься высоко и тянуться к небу пока это будет нужно селекционеру, то бишь программисту. И значит, не имеет смысла снова и снова изобретать велосипед, а остается лишь совершенствовать его и нагромождать необходимые надстройки, дополнения, а это экономит время при разработке программ.
  
При этом действует правило, что один объектный тип может служить предком для неограниченного числа объектовых типов-потомков, но каждый объектный тип может быть потомком только одного родителя, который указывается в скобках.
  
Вернемся к нашим баранам. Мы остановились на объявлении нового объектного типа TFrame, который помимо всего прочего унаследовал от своего предка TLine методы Enable и Disable. Да, но ведь эти методы рисуют линию, а объект TFrame, по идее, должен рисовать прямоугольник. В этом случае можно поступить по старинке, то есть добавить два метода, которые бы рисовали рамку, и назывались другими именами. Но это было бы отступлением от объектно-ориентированного стиля программирования. Более элегантным решением может послужить переопределение методов Enable и Disable, то есть замена их новыми методами с аналогичными именами, а за одно и переопределим метод Create, чтобы выполнялась инициализация поля FFilled:
 
type
       TFrame = object (TLine)
         FFilled : boolean;
        procedure Create( ALeft, ATop, ARight, ABottom : integer );
        procedure SetFilled( AFilled : boolean );
        procedure Enable;
        procedure Disable;
        function Filled : boolean;
       end;
 
procedure TFrame.Create( ALeft, ATop, ARight, ABottom : integer );
begin
{ вызываем родительский метод }
TLine.Create( ALeft, ATop, ARight, ABottom );
FFilled := false;
end;
 
procedure TFrame.SetFilled( AFilled : boolean );
 
Пока приведу лишь исходный код метода Create, а остальные методы будут рассмотрены позднее.
  
Выше мы переопределили методы, тем самым перекрыли родительские новыми методами. При этом в объекте-потомке под теми же именами будут известны только его собственные методы, а унаследованные (inherited) методы доступны посредством указания составного имени, состоящего из имени родительского типа и имени метода, разделенного точкой.
  
Из нового метода Create видно, что в нем используется вызов унаследованного метода TLine.Create, чтобы не делать двойную работу. Начиная с версии Turbo Pascal 7.0, введено служебное слово inherited, которое позволяет осуществить вызов перекрытого унаследованного метода. Тогда для седьмой версии метод Create может иметь следующий вид:
 
procedure TFrame.Create( ALeft, ATop, ARight, ABottom : integer );
begin
{ вызываем родительский метод }
inherited Create( ALeft, ATop, ARight, ABottom );
FFilled := false;
end;
 
что выглядит гораздо нагляднее.
  
Помимо этого, новый метод может иметь совсем другой интерфейс входных параметров, чем у одноименного унаследованного метода.
  
В отличие от методов, поля переопределить нельзя, так как они в обязательном порядке наследуются объектом-потомком, хотя можно добавлять новые поля, но при этом их имена не должны совпадать с именами полей родителя.
 

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

 
Кроме эквивалентных объектных типов, объявленных как type TObj1=TObj2, совместимыми по присваиванию являются объектные типы, состоящие в родстве, при этом можно присваивать потомка родителю:
 
var
     Line : TLine;
      Frame : TFrame;
 
begin
Line := Frame;
 
Такое же правило распространяется и на указатели объектных типов. При подобном присвоении значениями полей объекта-потомка модифицируются только одноименные поля объекта-родителя.
 
Продолжение следует…
 
© Владислав Демьянишин
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ.
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 33): Средства объектно-ориентированного программирования
 
 
 
 
 
 
На главную страницу На предыдущую страницу На начало страницы