По первому тесту выполнил дважды задания. Результат получается правильный (проверял калькулятором). Пишет, что "Задание не проверено" и предлагает повторить. |
Объектно-ориентированное программирование
9.3 Наследование и полиморфизм
Второй основополагающей составляющей объектно-ориентированного программирования является наследование. Смысл наследования заключается в следующем: если нужно создать новый класс, лишь немного отличающийся от старого, то нет необходимости в переписывании заново уже существующих полей и методов. В этом случае объявляется новый класс, который является наследником уже имеющегося, и к нему добавляются новые поля, методы и свойства. При создании новый класс является наследником членов ранее определённого базового класса (родителя). Класс-наследник является производным от базового, причём он сам может выступать в качестве базового класса для вновь создаваемых классов.
В Object Pascal все классы являются потомками класса TObject. Поэтому если вы создаёте дочерний класс непосредственно от класса TObject, то в определении его можно не упоминать.
Производный класс наследует от класса-предка поля и методы. Если имеет место совпадение имён методов, то говорят, что они перекрываются. В зависимости от того, какие действия происходят при вызове, методы делятся на следующие группы:
- статические методы;
- виртуальные методы;
- динамические методы.
По умолчанию все методы статические. Эти методы полностью перекрываются в классах-потомках при их переопределении. При этом можно полностью изменить объявление метода. Если обращаться к такому методу у объекта базового класса, то будет работать метод класса-родителя. Если обращаться к методу у производного класса, то будет работать новый метод.
Виртуальные и динамические методы имеют в базовом и производном классе те же имена и типы. В классах-наследниках эти методы перегружены. В зависимости от того, с каким классом работают, соответственно и вызывается метод этого класса.
Основная разница между виртуальными и динамическими методами — в способе их вызова. Информация о виртуальных методах хранится в таблице виртуальных методов VMT. В VMT хранятся виртуальные методы данного класса и всех его предков. При создании потомка класса вся VMT предка переносится в потомок и там к ней добавляются новые методы. Поиск нужного метода занимает мало времени, так как класс имеет всю информацию о своих виртуальных методах. Динамические методы не дублируются в таблице динамических методов DMT потомка. DMT класса содержит только методы, объявленные в этом классе. При вызове динамического метода сначала осуществляется поиск в DMT данного класса, и если метод не найден — то в DMT предка класса и т. д. Таким образом использование виртуальных методов требует больший расход памяти из-за необходимости хранения массивных VMT всех классов, зато они вызываются быстрее.
Изменяя алгоритм того или иного метода в производных классах, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимо перегрузить его в потомке, т. е. объявить в наследнике одноимённый метод и реализовать в нём нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноимённых метода, имеющих разную алгоритмическую основу. Это называется полиморфизмом объектов.
Виртуальные и динамические методы объявляются так же, как и статические, только в конце описания метода добавляются служебные слова virtual или dynamic:
type
...
...
end;
Чтобы перегрузить в классе-наследнике виртуальный метод, нужно после его объявления написать ключевое слово override:
type
имя_класса_наследника=class (имя_класса_родителя)
...
...
end;
Рассмотрим наследование в классах на следующем примере. Создадим базовый класс Ttriangle (треугольник ) с полями классакоординаты вершин треугольника. В классе будут следующие методы:
- Proverka() — метод проверки существования треугольника (если 3 точки лежат на одной прямой, то треугольник не существует);
- Perimetr() — метод вычисления периметра треугольника;
- Square() — метод вычисления площади;
- Методы вычисления длин сторон a(), b(), c();
- Set_Tr() — метод получения координат;
- Show() — метод формирования сведений о треугольнике.
На основе этого класса создадим производный класс R_TTriangle (равносторонний треугольник), который наследует все поля и методы базового класса, но методы проверки и формирования сведений о фигуре перегружаются по другому алгоритму.
На форме поместим метку, кнопку и по 6 компонент типа TEdit для ввода координат вершин для двух треугольников. После щелчка по кнопке создаются два объекта типа "треугольник" и "равносторонний треугольник", вычисляются периметр и площадь каждого треугольника, и результаты выводятся ниже в компоненты Label15 и Label16. Далее приведён листинг программы с комментариями.
unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls; type { TForm1 } TForm1 = class (TForm) Button1 : TButton; //кнопка "Расчёт" //массивы x1, y1 - координаты вершин //первого треугольника (частный случай) //массивы x1, y1 - координаты вершин //второго треугольника (равностороннего) Edit1 : Tedit; //для ввода координаты x1[1] Edit10 : TEdit; //для ввода координаты y2[2] Edit11 : TEdit; //для ввода координаты x2[3] Edit12 : TEdit; //для ввода координаты y2[3] Edit2 : TEdit; //для ввода координаты y1[1] Edit3 : TEdit; //для ввода координаты x2[2] Edit4 : TEdit; //для ввода координаты y1[2] Edit5 : TEdit; //для ввода координаты x1[3] Edit6 : TEdit; //для ввода координаты y1[3] Edit7 : TEdit; //для ввода координаты x2[1] Edit8 : TEdit; //для ввода координаты y2[1] Edit9 : TEdit; //для ввода координаты x2[2] Label1 : TLabel; Label10 : TLabel; Label11 : TLabel; Label12 : TLabel; Label13 : TLabel; Label14 : TLabel; Label15 : TLabel; Label16 : TLabel; Label2 : TLabel; Label3 : TLabel; Label4 : TLabel; Label5 : TLabel; Label6 : TLabel; Label7 : TLabel; Label8 : TLabel; Label9 : TLabel; procedure Button1Click ( Sender : TObject ); private { private declarations } public { public declarations } end; //объявление базового класса "треугольник" type TTriangle=class Private //массивы, в которых хранятся координаты вершин треугольника x, y : array [ 0.. 2 ] of real; Public constructor Create; //конструктор //метод получения исходных данных procedure Set_Tr ( a, b : array of real ); //Методы вычисления сторон треугольника. function a ( ) : real; function b ( ) : real; function c ( ) : real; //Виртуальный метод проверки существования //треугольника, который будет перегружен //в производном классе. function Proverka ( ) : boolean; virtual; function Perimetr ( ) : real; //метод вычисления периметра function Square ( ) : real; //метод вычисления площади //виртуальный метод формирования сведений о треугольнике function Show ( ) : string; virtual; end; //объявление производного класса "равносторонний треугольник" type R_TTriangle=class ( TTriangle ) public //перегружаемые методы проверки, что треугольник является //равносторонним, //и формирования сведений о треугольнике function Proverka ( ) : boolean; override; function Show ( ) : string; override; end; var Form1 : TForm1; //объявление переменной типа класс "треугольник" Figura1 : Ttriangle; //объявление переменной типа класс "равносторонний треугольник" Figura2 : R_TTriangle; implementation //Конструктор, который обнуляет //массивы координат. constructor TTriangle. Create; var i : integer; begin for i :=0 to 2 do begin x [ i ] : = 0; y [ i ] : = 0; end; end; //Метод получения координат вершин. procedure TTriangle. Set_Tr ( a, b : array of real ); var i : integer; begin for i :=0 to 2 do begin x [ i ] : = a [ i ]; y [ i ] : = b [ i ]; end; end; //Методы вычисления сторон треугольника a, b, c function TTriangle. a ( ) : real; begin a:= sqrt ( sqr ( x [1] - x [ 0 ] ) + sqr ( y [1] - y [ 0 ] ) ); end; function TTriangle. b ( ) : real; begin b:= sqrt ( sqr ( x [2] _ x [ 1 ] ) + sqr ( y [2] _ y [ 1 ] ) ); end; function TTriangle. c ( ) : real; begin c := sqrt ( sqr ( x [0] - x [ 2 ] ) + sqr ( y [0] - y [ 2 ] ) ); end; //Метод вычисления периметра треугольника. function TTriangle. Perimetr ( ) : real; begin Perimetr :=a()+b()+c( ); end; //Функции вычисления площади треугольника. function TTriangle. Square ( ) : real; var p : real; begin p:= Perimetr ( ) / 2; //полупериметр Squire := sqrt ( ( p -a ( ) ) * ( p - b ( ) ) * ( p - c ( ) ) ); end; //Метод проверки существования треугольника: //если в уравнение прямой, проходящей через //две точки подставить координаты третьей точки //и при этом получится равенство, значит три //точки лежат на одной прямой и построение //треугольника невозможно. function TTriangle. Proverka ( ) : boolean; begin if ( x [0] - x [ 1 ] ) / ( x [0] - x [ 2 ] ) = ( y [0] - y [ 1 ] ) / ( y [0] - y [ 2 ] ) then Proverka := false else Proverka := true end; //метод формирования строки - сведений о треугольнике function TTriangle. Show ( ) : string; begin //если треугольник существует, //то формируем строку сведений о треугольнике if Proverka ( ) then Show:= ’ Tr ’+chr (13)+ ’ a= ’+FloatToStrF ( a ( ), ffFixed,5,2)+ chr (13)+ ’ b= ’+FloatToStrF ( b ( ), ffFixed,5,2)+ chr (13)+ ’ c= ’+FloatToStrF ( c ( ), ffFixed,5,2)+ chr (13)+ ’P= ’+ FloatToStrF ( Perimetr ( ), ffFixed,5,2)+ chr (13)+ ’ S= ’+ FloatToStrF ( Square ( ), ffFixed, 5, 2 ) else Show:= ’ Not Triangle ’; end; //Метод проверки существования //равностороннего треугольника. function R_TTriangle. Proverka ( ) : boolean; begin if ( a()=b ( ) ) and ( b()= c ( ) ) then Proverka := true else Proverka := false end; //Метод формирования сведений //о равностороннем треугольнике. function R_TTriangle. Show ( ) : string; begin //если треугольник равносторонний, //то формируем строку сведений if Proverka ()= true then Show:= ’ Tr ’+chr (13)+ ’ a= ’+FloatToStrF ( a ( ), ffFixed,5,2)+ chr (13)+ ’P= ’+FloatToStrF ( Perimetr ( ), ffFixed,5,2)+ chr ( 1 3 ) + ’ S= ’+FloatToStrF ( Square ( ), ffFixed, 5, 2 ) else Show:= ’ Not R_Triangle ’; end; { TForm1 } procedure TForm1. Button1Click ( Sender : Tobject ); //массивы x1, y1 - координаты треугольника //массивы x2, y2 - координаты равностороннего треугольника var x1, y1, x2, y2 : array [ 1.. 3 ] of real; s : string; begin //чтение координат треугольников из полей ввода диалогового окна x1 [ 1 ] : = StrToFloat ( Edit1. Text ); y1 [ 1 ] : = StrToFloat ( Edit2. Text ); x1 [ 2 ] : = StrToFloat ( Edit3. Text ); y1 [ 2 ] : = StrToFloat ( Edit4. Text ); x1 [ 3 ] : = StrToFloat ( Edit5. Text ); y1 [ 3 ] : = StrToFloat ( Edit6. Text ); x2 [ 1 ] : = StrToFloat ( Edit7. Text ); y2 [ 1 ] : = StrToFloat ( Edit8. Text ); x2 [ 2 ] : = StrToFloat ( Edit9. Text ); y2 [ 2 ] : = StrToFloat ( Edit10. Text ); x2 [ 3 ] : = StrToFloat ( Edit11. Text ); y2 [ 3 ] : = StrToFloat ( Edit12. Text ); //инициализация объекта класса "треугольник" Figura1 := TTriangle. Create; //инициализация объекта класса "равносторонний треугольник" Figura2 :=R_TTriangle. Create; Figura1. Set_Tr ( x1, y1 ); Figura2. Set_Tr ( x2, y2 ); //вызов методов формирования сведений и вывод строки на форму s := Figura1. Show ( ); Label15. Caption := S; s := Figura2. Show ( ); Label16. Caption := S; //уничтожение объектов Figura1. Free; Figura2. Free; end; initialization {$I unit1.lrs} end.
Результаты работы программы представлены на рис. 9.3.
Абстрактный метод — это виртуальный или динамический метод, реализация которого не определена в том классе, где он объявлен. Предполагается, что этот метод будет перегружен в классе-наследнике. Вызывают метод только в тех классах, где он перезагружен. Абстрактный метод объявляется при помощи служебного слова abstract после слов virtual или dynamic, например:
Рассмотрим следующий пример. Создадим базовый класс TFigure (фигура). На основании этого класса можно построить производные классы для реальных фигур (окружность, четырёхугольник и т. д.). В нашем примере рассмотрим два класса-наследника TCircle (окружность) и TRectangle (прямоугольник). На форму поместим кнопку. При щелчке по кнопке инициализируются 2 объекта типа "окружность" и "прямоугольник", рассчитываются параметры фигур: периметр (длина окружности) и площадь. Результаты выводятся в компоненты Label1 и label2. Ниже приведён листинг программы.
unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls; type { TForm1 } TForm1 = class (TForm) Button1 : TButton; Label1 : TLabel; Label2 : TLabel; procedure Button1Click ( Sender : TObject ); private { private declarations } public { public declarations } end; //объявление базового класса "фигура" type TFigure=class private n : integer; //количество сторон фигуры p : array of real; //массив длин сторон фигуры public //абстрактный конструктор //в каждом производном классе будет перегружаться constructor Create; virtual; abstract; function Perimetr ( ) : real; //метод вычисления периметра //абстрактный метод вычисления площади, в каждом //производном классе будет перегружаться реальным методом function Square ( ) : real; virtual; abstract; //абстрактный метод формирования сведений о фигуре //в каждом производном классе будет перегружаться function Show ( ) : string; virtual; abstract; end; //объявление производного класса "окружность" type TCircle=class ( TFigure ) public constructor Create; override; function Perimetr ( ) : real; function Square ( ) : real; override; function Show ( ) : string; override; end; //объявление производного класса "прямоугольник" type TRectangle=class ( TFigure ) public constructor Create; override; function Square ( ) : real; override; function Show ( ) : string; override; end; var Form1 : TForm1; Figura1 : Tcircle; //объект типа класса "окружность" Figura2 : TRectangle; //объект типа класса "прямоугольник" implementation //Описание метода вычисления //периметра для базового класса. function TFigure. Perimetr ( ) : real; var i : integer; s : real; begin s : = 0; for i :=0 to n-1 do s := s+p [ i ]; Perimetr := s; end; //описание конструктора в классе "окружность" //перезагрузка абстрактного родительского конструктора constructorTCircle. Create; begin //количество сторон для окружности 1 n : = 1; //выделяем память под 1 элемент массива SetLength ( p, n ); p [ 0 ] : = 5; //сторона - радиус окружности end; //Перезагрузка метода вычисления периметра. function TCircle. Perimetr ( ) : real; begin Perimetr :=2 * Pi * p [ 0 ]; //вычисление длины окружности end; //Перезагрузка метода вычисления площади function TCircle. Square ( ) : real; begin Square := Pi * sqr ( p [ 0 ] ); end; //Описание метода формирования //строки сведений об окружности. //Перезагрузка родительского //абстрактного метода. function TCircle. Show ( ) : string; begin Show:= ’ Circle ’+chr (13)+ ’ r= ’+ FloatToStr ( p [ 0 ] ) + chr (13)+ ’P= ’+ FloatToStr ( Perimetr ())+ chr (13)+ ’ S= ’+FloatToStr ( Square ( ) ); end; //Описание конструктора в классе прямоугольник. //перезагрузка абстрактного //родительского конструктора. constructor TRectangle. Create; begin n : = 2; //количество сторон - две SetLength ( p, n ); //выделение памяти под два элемента p [ 0 ] : = 4; p [ 1 ] : = 2; //длины сторон end; //перезагрузка абстрактного родительского метода //вычисления площади фигуры function TRectangle. Square ( ) : real; begin Square :=p [ 0 ] * p [ 1 ]; end; //Описание метода формирования //сведений о прямоугольнике. //перезагрузка родительского //абстрактного метода. function TRectangle. Show ( ) : string; begin Show:= ’ Rectangle ’+chr (13)+ ’ a= ’+ FloatToStr ( p [ 0 ] ) + ’ b= ’+ FloatToStr ( p [ 1 ] ) + chr (13)+ ’P= ’+FloatToStr ( Perimetr ())+ chr (13)+ ’ S= ’+FloatToStr ( Square ( ) ); end; { TForm1 } procedure TForm1. Button1Click ( Sender : TObject ); var s : string; begin //инициализация объекта типа "окружность" Figura1 := TCircle. Create; //инициализация объекта типа "прямоугольник" Figura2 := TRectangle. Create; //формирование сведений о фигурах и вывод результатов на форму s := Figura1. Show ( ); label1. Caption := s; s := Figura2. Show ( ); label2. Caption := s; Figura1. Free; Figura2. Free; end; initialization {$I unit1.lrs} end.