Е. К. Пугачев Объектно-ориентированное программирование Под общей редакцией Ивановой Г. С. Рекомендовано Министерством общего и профессионального образования Российской Федерации в качестве учебник

Вид материалаУчебник

Содержание


Простой полиморфизм
Type Lot = Object(Symb)
Begin Symbvar.Init(1,1,80,25,7,1,40,12,'A')
Сложный полиморфизм.
Program MetVirt
Procedure Run
End; Procedure Win.Print; Begin End
Пример 2.22. Использование процедуры с полиморфным объектом
Program PrPolOb
Procedure Print; virtual
Var V1:Win; V2:Symb; V3:Lot
TypeOf: TypeOf
SizeOf: SizeOF
Const V1:Win=(X1:20;Y1:5;X2:40;Y2:15;Cf:12)
Подобный материал:
1   ...   7   8   9   10   11   12   13   14   ...   39

2.3.Полиморфизм


Borland Pascal реализует механизмы простого и сложного полиморфизма.

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

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

Рассмотрим случай применения простого полиморфизма в языке Borland Pascal 7.0.

Пример 2.20. Применение простого полиморфизма

Пусть необходимо разработать программу, которая выводит в окно не один символ, а последовательность одинаковых символов. Для построения нужного класса используем уже имеющиеся классы Win и Symb: наследуем класс Lot от класса Symb. При этом необходимо заменить метод Print, описанный в классе Symb (рис. 2.2). Для объектов класса Lot он будет выводить не один символ, а опеределенное количество символов, заданное в поле N этого класса.



Рис. 2.35. Иерархия классов примера 2.4

Program WinSymbLot;

Uses Crt;

{. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .}

{Описание класса Win и его наследника Symb из примера 2.3 . .}

{. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .}

^ Type

Lot = Object(Symb) {класс-потомок Lot}

N : Word; {новое поле - количество символов}

Procedure Init( Xn1,Yn1,Xn2,Yn2,Cfn,Csn,Cl,Ln:Byte;Sm:Char; Nk:word); {инициализация полей}

Procedure Print ; {переопределенный метод вывода символа}

End;

Procedure Lot.Init;

Begin Symb.Init(Xn1,Yn1,Xn2,Yn2,Cfn,Csn,Cl,Ln,Sm);

N := Nk; {инициализация нового поля}

End;

Procedure Lot.Print;

Var i:byte;

Begin

for i:=1 to N do

begin Symb.Print; {вызвать метод родителя для вывода символа}

inc(Col); inc(Line) {изменить позицию вывода}

end;

End;

Var Symbvar:Symb; {объявление объекта класса Symb }

Lotvar : Lot; {объявление объекта класса Lot }

^ Begin

Symbvar.Init(1,1,80,25,7,1,40,12,'A'); {инициализировать поля}

Symbvar.MakeWin; {изобразить окно}

Symbvar.Print; {вывести символ – метод класса Symb}

Readkey;

Lotvar.Init(1,1,80,25,7,1,40,12,'B',10); {инициализировать поля}

Lotvar.MakeWin; {изобразить окно}

Lotvar.Print; {вывести символы – метод класса Lot}

Readkey;

End.

По существу метод Print класса Symb и метод Print класса Lot разные методы. Для объекта каждого класса вызывается метод, определенный в этом классе.

По правилам Borland Pascal статические полиморфные методы могут иметь различные списки параметров в отличие от виртуальных полиморфных методов, рассматриваемых далее.

^ Сложный полиморфизм. При сложном полиморфизме конкретный аспект полиморфного метода также определяется типом объекта, для которого он вызван, но только не на этапе компиляции, а на этапе выполнения программы (позднее связывание).

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

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

Полиморфные методы, для которых применяется позднее связывание, называют виртуальными.

Для описания виртуальных методов используется служебное слово virtual:

Procedure <имя метода>(<список параметров>); virtual;

Function <имя метода> (<список параметров>):<тип функции>; virtual;

При использовании виртуальных методов должны выполняться следующие правила.

1. Если в некотором классе метод описан как виртуальный, то все производные классы, включающие метод с тем же именем, должны описать этот метод как виртуальный. Нельзя заменить виртуальный метод статическим.

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

3. Класс, содержащий виртуальные методы должен включать специальный статический метод - конструктор. Для этого метода слово Procedure в объявлении и реализации должно быть заменено словом Constructor. Конструктор неявно выполняет настройку механизма позднего связывания (обеспечивает связь объекта с ТВМ – раздел 1.6). Этот метод должен быть вызван до первого обращения к виртуальному методу, иначе происходит «зависание» компьютера. Как правило, конструктор используют для инициализации полей объекта и, соответственно, называют Init.

Вызов виртуальных методов осуществляется через ТВМ, что требует дополнительного времени. В результате программа будет работать медленнее, чем при использовании статических методов. Однако использование виртуальных методов позволяет существенно увеличить гибкость описания классов.

Фактически использование виртуальных методов обязательно в трех случаях:

1) полиморфный метод вызывается из статического метода родительского класса;

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

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

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

Продемонстрируем использование виртуальных методов, несколько изменив иерархию классов примера 2.4.

Пример 2.21. Вызов виртуальных методов из методов базового класса

Если при разработке иерархии классов Win – Symb – Lot учесть, что окно всегда рисуется для вывода символов, то в описание класса Win стоит включить метод Run, который изображает окно и выводит в него символы. Этот метод должен вызывать методы изображения окна MakeWin и вывода символов в окно Print. Метод MakeWin в классах-потомках не переопределяется (статический метод). Метод Print в классах Symb и Lot переопределяется (полиморфный метод). В классе Win метод Print не предусмотрен, поэтому добавим в этот класс абстрактный («пустой», не выполняющий никаких действий) метод Print.

Метод Run будет наследоваться классами Symb и Lot. Конкретный тип объекта, для которого вызывается метод Run, будет определен только на этапе выполнения, значит и нужный аспект полиморфного метода Print можно будет определить только на этапе выполнения программы, и, следовательно, метод Print должен быть объявлен виртуальным (случай 1 из рассмотренных выше).

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

^ Program MetVirt;

Uses Crt;

Type

Win = Object {родительский класс Окно}

X1,Y1,X2,Y2,Cf: byte;

Constructor Init(Xn1,Yn1,Xn2,Yn2,Cfn:byte);

Procedure MakeWin; {изображение окна}

Function GetSizeX : byte; {определение размера окна по X}

Function GetSizeY : byte; {определение размера окна по Y}

^ Procedure Run; {изображение окна с текстом}

Procedure Print; virtual; {абстрактный виртуальный метод}

End;

Constructor Win.Init;

Begin X1:=Xn1; Y1:=Yn1; X2:=Xn2; Y2:=Yn2; Cf:=Cfn; End;

Procedure Win.MakeWin;

Begin Window(X1,Y1,X2,Y2); Textbackground(Cf); Clrscr End;

Function Win.GetSizeX; Begin GetSizeX:=X2-X1+1 End;

Function Win.GetSizeY; Begin GetSizeY:=Y2-Y1+1 End;

Procedure Win.Run;

Begin MakeWin; {вызов статического метода изображения окна}

Print {вызов аспекта виртуального полиморфного метода}

^ End;

Procedure Win.Print; Begin End; {резервный – «пустой» метод}

Type

Symb = Object(Win) {класс-потомок «символ»}

Cs,Col,Line:byte; Sym:char;

Constructor Init(Xn1,Yn1,Xn2,Yn2,Cfn,Csn,Cl,Ln:byte;Sm:Char);

Procedure Print; Virtual; {вывод символа}

End;

Constructor Symb.Init;

Begin Win.Init(Xn1,Yn1,Xn2,Yn2,Cfn);

Cs:=Csn; Col:=Cl; Line:=Ln; Sym:=Sm;

End;

Procedure Symb.Print;

Begin TextColor(Cs); Gotoxy(Col,Line); Write(Sym) End;

Type

Lot = Object(Symb) {класс-потомок Lot}

N : Word;

Constructor Init

(Xn1,Yn1,Xn2,Yn2,Cfn,Csn,Cl,Ln:Byte;Sm:Char; Nk:word);

Procedure Print; virtual;

End;

Constructor Lot.Init;

Begin Symb.Init(Xn1,Yn1,Xn2,Yn2,Cfn,Csn,Cl,Ln,Sm); N := Nk; End;

Procedure Lot.Print;

Var i:byte;

Begin for i:=1 to N do begin Symb.Print; inc(Col); inc(Line) end; End;

Var V1:Win; V2:Symb; V3:Lot;

Begin V1.Init(20,5,40,15,12); {конструировать объект V1}

V2.Init(10,3,60,20,3,1,30,10,'A'); {конструировать объект V2}

V3.Init(1,1,80,25,5,1,40,12,'B',10); {конструировать объект V3}

V1.Run; {вызвать «пустой» аспект метода Print} Readkey;

V2.Run; {вызвать метод Print класса Symb} Readkey;

V3.Run; {вызвать метод Print класса Lot} Readkey;

End.

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

^ Пример 2.22. Использование процедуры с полиморфным объектом

Вместо метода Run можно описать процедуру с полиморфным объектом PolOb, в которой параметром является переменная типа Win. Такой подход также дает возможность использовать процедуру PolOb, а в ней конкретно методы MakeWin и Print, для класса-родителя Win и всех его потомков. При этом нужный аспект полиморфного метода Print будет определен по типу переданного в процедуру параметра (V1,V2 или V3).

Если убрать слово virtual в методе родителя Print, то при обращении к любому потомку метод Print будет взят родительский (в данном случае «пустой»).

^ Program PrPolOb;

Uses crt;

Type

Win = Object {родительский класс Окно}

X1,Y1,X2,Y2,Cf: byte;

Constructor Init(Xn1,Yn1,Xn2,Yn2,Cfn:byte);

Procedure MakeWin; {изображение окна}

Function GetSizeX : byte; {определение размера окна по X}

Function GetSizeY : byte; {определение размера окна по Y}

^ Procedure Print; virtual; {абстрактный виртуальный метод}

End;

Constructor Win.Init;

Begin X1:=Xn1; Y1:=Yn1; X2:=Xn2; Y2:=Yn2; Cf:=Cfn; End;

Procedure Win.MakeWin;

Begin Window(X1,Y1,X2,Y2); Textbackground(Cf); Clrscr End;

Function Win.GetSizeX; Begin GetSizeX:=X2-X1+1 End;

Function Win.GetSizeY; Begin GetSizeY:=Y2-Y1+1 End;

Procedure Win.Print; Begin End;

{. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . }

{ . . . . . . . Описание классов Symb и Lot из примера 2.5. . . . .}

{ . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . }

^ Var V1:Win; V2:Symb; V3:Lot; {экземпляры родственных классов}

{Процедура с полиморфным объектом Obj}

Procedure PolOb(Var Obj: Win);

Begin

Obj.MakeWin; {вызвать обычный статический метод}

Obj.Print {вызвать виртуальный полиморфный метод}

End;

Begin

V1.init(20,5,40,15,12); {конструировать объект V1}

V2.init(10,3,60,20,3,1,30,10,'A'); {конструировать объект V2}

V3.init(1,1,80,25,5,1,40,12,'B',10); {конструировать объект V3}

PolOb(V1); {выполнить с объектом V1} Readkey;

PolOb(V2); {выполнить с объектом V2} Readkey;

PolOb(V3); {выполнить с объектом V3} Readkey;

End.

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

{. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . }

{ . . . . . . . Описание класса Win и его наследников . . . . . . . . . . .}

{ . . . . . . . . . . . . . . Symb и Lot из примера 2.5. . . . . . . . . . . . . . . . }

Type W=^Win; {указатель на родительский класс}

Var V:W; V2:Symb; V3:Lot;

Procedure PolOb(Var Obj: W); Begin Obj^.MakeWin; Obj^.Print End;

Begin

New(V); V1^.Init(20,5,40,15,5);

V2.Init(10,3,60,20,3,1,30,10,'A');

V3.Init(1,1,80,25,5,3,40,12,'B',10);

PolOb(V1); Readkey;

V1:=@V2; {передать адрес 1-го потомка}

PolOb(V1); {выводит: А} Readkey;

V1:=@V3; {передать адрес 2-го потомка}

PolOb(V1); {выводит: ВВВВВВВВВВ} Readkey;

End.

Тип полиморфного объекта в программе можно определить с помощью функции ^ TypeOf:

TypeOf (<имя объекта>) : Pointer ;

или TypeOf (<имя класса>) : Pointer ;

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

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

if TypeOf(Self)=TypeOf(<ИмяЭкземпляра>) then . . . ;

Чтобы определить фактический размер объекта можно применить функцию ^ SizeOf:

SizeOF (<имя объекта>): integer;

или SizeOF (<имя класса>): integer;

Примечание. Для проверки корректности экземпляров программа должна компилироваться в режиме {$R+}. При этом генерируется вызов подпрограммы проверки правильности ТВМ перед каждым вызовом виртуального метода. Это замедляет работу программы, поэтому рекомендуется включать {$R+} только во время отладки.

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

^ Const V1:Win=(X1:20;Y1:5;X2:40;Y2:15;Cf:12);

V2:Symb=(X1:10;Y1:3;X2:60;Y2:20;Cf:3;

Cs:1;Col:30;Line:10;Sym:'A');

V3:Lot=(X1:1;Y1:1;X2:80;Y2:25;Cf:5;

Cs:1;Col:40;Line:12;Sym:'B';N:10);

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