Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
пособие1.doc
Скачиваний:
1
Добавлен:
14.08.2019
Размер:
1.32 Mб
Скачать

Глава 10 оБъектно-ориентированное программирование

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

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

10.1. Объекты. Основные понятия

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

Type Point = object

x,y:integer;

visible:boolean;

end;

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

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

Подпрограммы, определенные в объектовом типе, называются методами объекта (methods).

Технически определение подпрограмм в объектовых типах делается так: непосредственно в объектовом типе задаются только заголовки подпрограмм, а полные их описания должны быть заданы отдельно, причем имя подпрограммы-метода формируется из имени объектового типа - "хозяина", символа "точка" и имени подпрограммы, например:

Type Point = object

x,y:integer;

visible:boolean;

procedure Create(a,b:integer);

procedure SwitchOn;

procedure SwitchOff;

procedure Move(dx,dy:integer);

function GetX:integer;

function GetY:integer;

end;

Procedure Point.Create(a,b:integer);

begin

x:=a; y:=b; visible:=false

end;

Procedure Point.SwitchOn;

begin

visible:=true; PutPixel(x,y,GetColor)

end;

.........................

Function point.GetX:integer;

begin

Getx:x

end;

..........................

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

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

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

Var OnePoint:point;

и в дальнейшем оперировать с этим экземпляром посредством его методов:

OnePoint.Create(100,200);

OnePoint.SwichOn;

OnePoint.Move(20,-10);

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

with OnePoint do

begin

Create(100,200); SwitchOn; Move(20,-10)

end;

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

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

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

Традиционный стиль программирования допускает два решения.

Во-первых, можно ввести для круга совершенно новую структуру, повторив в ней (может быть, под другими именами) те же поля X, Y и visible и добавив новое поле Radius. Во-вторых, можно сконструировать структуру для круга, использовав в ней поле с типом, ранее определенным для точки (сделать структуру в структуре). Оба подхода вполне приемлемы, однако объектно-ориентированное программирование предлагает иной подход, который по ряду причин является гораздо более предпочтительным.

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

Type Circle = object (Point)

Radius:integer

end;

Задание родительского типа означает, что в объектовом типе Circle неявно присутствуют все поля из типа Point: аналогично, для переменной этого нового типа доступны все методы из Point:

Var OneCircle:circle;

begin

OneCircle.Create(100,200);

OneCircle.Radius:=30;

.....................

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

Type Ring = object(Circle)

Radius2:integer;

end;

Тип Ring наследует поле Radius из своего непосредственного родителя Circle, а также поля и методы из типа Point, который также считается (косвенным) предком для Ring. Длина такой цепочки наследования никак не ограничивается.

Вернемся к примеру с объектовым типом Circle. По правилу наследования тип Circle имеет в своем составе методы объекта-предка Point. Однако, легко видеть, что если методы GetX, GetY, возвращающие текущие значения координат, вполне применимы в типе-потомке, то, например, методы SwitchOn, SwitchOff не подходят для рисования круга. Поэтому, наряду с новым полем Radius, полное описание типа Circle должно содержать также собственные методы для рисования и удаления круга и его передвижения по экрану. Самым простым решением было бы ввести в новый тип такие методы, дав им некоторые новые имена. Но объектно-ориентированный подход позволяет определить новые методы со старыми именами, переопределив тем самым методы типа-родителя:

Type Circle = object(Point)

Radius : integer;

procedure Create (a,b,R:integer);

procedure SwitchOn;

procedure SwitchOff;

procedure Move(dx,dy:integer);

function GetR:integer;

end;

Procedure Circle.Create (a,b,R:integer);

begin

Point.Create(a,b); Radius:=r

end;

Procedure Circle.SwitchOn;

begin

visible:=true; Graph.Circle(x,y,Radius)

end;

..........................

Function Circle.GetR:integer;

begin

GetR:=Radius

end;

..........................

Такое определение объектового типа Circle содержит следующие элементы:

  • поля X,Y,visible, унаследованные от родительского типа Point;

  • собственное поле Radius;

  • метод Circle.Create, который инициализирует поля Circle. Этот метод переопределяет внутри типа Circle унаследованный от Point метод Point.Create. Для инициализации полей X,Y,visible используется вызов метода Point.Create, который доступен (унаследован) в типе Circle;

  • методы Circle.SwitchOn, Circle.SwitchOff и Circle.Move, которые, соответственно рисуют и удаляют круг и передвигают его. Эти методы полностью переопределяют (замещают) одноименные методы из Point, что имеет очевидный смысл: для рисования круга требуется иной алгоритм, нежели для рисования точки; в примере для этих целей используются стандартные процедуры из модуля Graph. Так как стандартная процедура вывода круга также имеет имя Circle, то для ее однозначной идентификации используется составное имя Graph.Circle;

  • новый (собственный) метод GetR для доступа к текущему значению радиуса круга;

  • два унаследованных (и не переопределенных) метода GetX, GetY для получения текущих координат центра круга.

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

10.3. Виртуальные методы

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

Procedure Move (dx,dy:integer);

begin

SwitchOff; x:=x+dx; y:=y+dy; SwitchOn

end;

Возникает желание определить метод Move для объектового типа Point с тем, чтобы тип-потомок Circle унаследовал его без переопределения. Необходимо в этом случае обеспечить возможность для Move вызывать либо метод Point.SwitchOff, либо метод Circle.SwitchOff в зависимости от того, какой объект вызывает Move. Такой механизм называется динамическим (или поздним) связыванием и достигается введением виртуальных методов.

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

Type Point = object

.....

Constructor Create(a,b:integer);

procedure SwitchOn;virtual;

procedure SwitchOff;virtual;

procedure Move(dx,dy:integer);

.....

end;

Circle = object(Point)

.....

Constructor Create(a,b:integer);

procedure SwitchOn;virtual;

procedure SwitchOff;virtual;

.....

end;

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

10.4. Конструкторы и деструкторы. Динамические объекты

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

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

Var CirclePtr : ^Circle;

Begin

New(CirclePtr);

CirclePtr^.Create(100,200,35);

...............

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

New(CirclePtr, Create(100,200,35));

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

Dispose(PointPtr);

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

Использование деструкторов необходимо, если экземпляр объекта был создан динамически (процедурой New). В этом случае завершение работы нужно производить посредством расширенного вида процедуры Dispose, вторым параметром которой является вызов деструктора. Например, если PointPtr - указатель на экземпляр объектового типа Point, а Point.Done - деструктор этого типа, то можно совместить операцию освобождения памяти с завершающими действиями с экземпляром:

Dispose(PointPtr,Done);

10.5. Классы.

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

Описание нового класса похоже на описание новой структуры данных, только к полям (свойствам) добавляются методы — подпрограммы.

В Паскале для описания класса используется ключевое слово class. Type TMyClass = Сlass

Iteml: integer;

Item2: string;

function GetSum(n; integer): integer;

procedure Initializer-end;

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

procedure TMyClass.Initialize;

begin Iteml := 1; Item2 := "";

end;

Си++:

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

var Cl, C2'. TMyClass;

Доступ к свойствам объектов и к их методам осуществляется так же, как к полям записей, через точку:

Cl.Iteml := 5;

С2.Initialize;

х := Cl.GetSum(21) ;

Объединение данных с методами в одном типе (классе) называется инкапсуляцией.

Важнейшая характеристика класса — возможность создания на его основе новых классов с наследованием всех его свойств и методов и добавлением собственных. Класс, не имеющий предшественника, называется базовым.

Например, класс «животное» имеет свойства «название», «размер», методы «идти» и «размножаться». Созданный на его основе класс «кошка» наследует все эти свойства и методы, к которым дополнительно добавляется свойство «окраска» и метод «пить».

Наследование позволяет создавать новые классы, повторно используя уже готовый исходный код и не тратя времени на его переписывание.

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

Объектно-ориентированное программирование характеризуется тремя основными свойствами:

  • инкапсуляция (incapsulation), т.е. объединение в одном объекте данных и действий над ними;

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

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

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

  • использование более естественных понятий (таких как объект), простота введения новых понятий;

  • сокращение размера программ за счет того, что наследуемые свойства и действия можно не описывать многократно;

  • возможность создания библиотеки объектов;

  • сравнительно простая возможность внесения изменений в программу без изменения уже написанных частей;

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

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

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

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

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

Вопросы для самоконтроля

  1. Как в Паскале определяется объектовый тип?

  2. Для чего в языки программирования было введено понятие класса?

  3. В чем различие между классом и объектом?

  4. Поясните понятие инкапсуляции на бытовых примерах.

  5. Для чего применяется механизм наследования?

  6. Как полиморфизм модифицирует принцип наследования?

  7. Опишите использование принципов объектно-ориентированного программирования в средах быстрого проектирования.