Учебник по программированию.

Первые шаги. Язык программирования PascalABC.

Предыдущий параграф Назад в содержание Следующий параграф


§27. Наследование. Полиморфизм. Абстрактные методы.

Наследование.

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


type TBazoviyClass = class

       procedure P_1;

       begin

         writeln('Сработала процедура P_1');

       end;

     end

      

type TProizvodniyClass = class (TBazoviyClass)

       procedure P_2;

       begin

         writeln('Сработала процедура P_2');

       end;

     end;


var Ob:TProizvodniyClass:=new TProizvodniyClass;


begin

  Ob.P_1;

  Ob.P_2;

end.

__________________________________

Сработала процедура P_1

Сработала процедура P_2


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

Ещё одно достоинство наследования это то, что можно переопределять методы базового класса. Что это значит? Это значит, что вместо метода базового класса в производном классе можно описать метод с таким же именем. В таком случае при вызове метода с этим именем будет вызываться метод, описанный в производном классе. Здесь нужно быть осторожным, потому что если этот метод вызывается другим методом, описанным в базовом классе, то будет вызван метод из базового класса.

Здесь же стоит сказать, что при переопределении метода может возникнуть ситуация, когда внутри метода производного класса нужно вызвать переопределённый метод именно базового класса. В таком случае используется специальное слово inherited.

Следующий пример всё это наглядно демонстрирует:


type TBazoviyClass = class

       procedure P_2;

       begin

         P_1;

       end;

       procedure P_1;

       begin

         writeln('Сработал метод P_1');

       end;

     end;


type TProizvodniyClass = class (TBazoviyClass)

       procedure P_1;

       begin

         writeln('Сработал переопределённый метод P_1');

       end;

       procedure P_3;

       begin

         writeln('Сработал метод P_3');

         P_1;

         inherited P_1;

       end;

     end;


var Ob: TProizvodniyClass := new TProizvodniyClass;


begin

  Ob.P_1;

  Ob.P_2;

  Ob.P_3;

end.

______________________________________________________________

Сработал переопределённый метод P_1

Сработал метод P_1

Сработал метод P_3

Сработал переопределённый метод P_1

Сработал метод P_1


Кстати, в данном примере продемонстрировано то, что метод, описанный ниже по тексту, спокойно вызывается из метода описанного выше. Это тоже одно из преимуществ ООП.

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


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

type TModernKnopka = class (TKnopka)

       function Naezd(mx,my:integer):boolean;//Определяет находится ли

                                            //указатель мышки над кнопкой

         begin

           if (mx>x) and (mx<(x+Shir)) and (my>y) and (my<(y+Vis)) then

               Naezd:=true

             else

               Naezd:=false;

         end;

       procedure DrawPodsvetka;//Подсвечивает кнопку

         begin

           SetPenColor(clRed);

           DrawRectangle(x+2,y+2,x+Shir-2,y+Vis-2);

           SetPenColor(clBlack);

         end;

       procedure ClearPodsvetka;//Убирает подсветку с кнопки

         begin

           SetPenColor(clLightBlue);

           DrawRectangle(x+2,y+2,x+Shir-2,y+Vis-2);

           SetPenColor(clBlack);

         end;

     end


var Knopka_1:TModernKnopka:=new TModernKnopka(1,1,'Кнопка_1');

    Knopka_2:TModernKnopka:=new TModernKnopka(80,1,'Кнопка_2');

    Knopka_3:TModernKnopka:=new TModernKnopka(160,1,'Кнопка_3');


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


procedure MouseMove(x,y,mb:integer);

begin

  if mb=0 then

    begin

      if Knopka_1.Naezd(x,y) then

                    Knopka_1.DrawPodsvetka else Knopka_1.ClearPodsvetka;

      if Knopka_2.Naezd(x,y) then

                    Knopka_2.DrawPodsvetka else Knopka_2.ClearPodsvetka;

      if Knopka_3.Naezd(x,y) then

                    Knopka_3.DrawPodsvetka else Knopka_3.ClearPodsvetka;

    end;               

end;


begin

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

  OnMouseMove:=MouseMove;

end.


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


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

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

Для того, что бы было понятнее, опишем следующую ситуацию. Допустим, мы имеем базовый класс TBaz и его метод P_1 и метод P_2, который в своей работе вызывает метод P_1. Этот класс мы имеем в уже откомпилированном виде и ничего поменять в нём не можем. Однако нам необходимо, что бы метод P_1 имел другой алгоритм работы. Говоря другими словами нам просто нужно переписать метод P_1. Что делать? Если мы просто создадим производный класс TProiz и переопределим метод  P_1, то всё равно метод P_2 вызовет метод P_1 из базового класса:


type TBaz = class

        procedure P_1;

          begin

            writeln('Klass TBaz процедура P_1');

          end;

        procedure P_2;

          begin

            P_1;

          end;

      end;


     TProiz = class (TBaz)

       procedure P_1;

         begin

           writeln('Klass TProiz процедура P_1');

         end;

     end;   

var Ob:TProiz:=new TProiz;


begin

  Ob.P_2;

end.

__________________________________________________

Klass TBaz процедура P_1


Ещё раз сформулируем, что нам нужно. Нам необходимо, что бы метод P_1 имел другой алгоритм работы и при этом метод P_2 при вызове метода P_1 вызывал бы метод P_1 с этим другим алгоритмом.

Как это делается? Для того, что бы метод можно было переписать его нужно пометить словом virtual. Тот метод, на который мы меняем, должен находиться в классе потомке, иметь такое же имя и должен быть помечен словом override. Переменная создаётся типа базового класса, а вот объект создаётся конструктором производного класса. В таком случае вместо метода P_1 базового класса всегда и везде будет вызываться метод P_1 производного класса:


program Polimorfizm;

type TBaz = class

        procedure P_1; virtual;

          begin

            writeln('Klass TBaz процедура P_1');

          end;

      end;


     TProiz = class (TBaz)

       procedure P_1; override;

         begin

           writeln('Klass TProiz процедура P_1');

         end;

     end;

var Ob:TBaz:=new TProiz;


begin

  Ob.P_1;

end.

____________________________________________________

Klass TProiz процедура P_1


Получается, что объект создаётся конструктором производного класса, соответственно и метод будет создан из производного класса.

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

Небольшое отступление: как вы могли заметить, при описании методов класса слова begin и end сдвинуты влево относительно слова procedure на два символа. На мой взгляд, в таком виде код становится более читаемым. Поэтому предлагаю именно так оформлять свои программы.

Теперь вернёмся к нашим кнопкам и применим полиморфизм, усовершенствовав программу TriKnopki. Предположим, что у нас есть готовый откомпилированный  класс TModernKnopka. А наш заказчик попросил, что бы кнопки были не просто прямоугольными, а углы у них должны быть округлены. Соответственно нам необходимо переписать метод Draw в базовом классе. В данном случае просто создать производный класс и переопределить этот метод не даст нужного результата. Это потому, что метод Draw вызывается методами DrawNeNaj и DrawNaj, которые находятся в базовом классе. Поэтому здесь однозначно необходимо использовать полиморфизм.

Предположим, что метод  Draw уже был помечен словом virtual. В таком случае нам остаётся создать производный класс, например TOkruglKnopka, в котором опишем метод с таким же именем и помеченным словом override:


program TriOkruglKnopki;

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

type

  TKnopka = class

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

    procedure Draw;virtual;//Процедура рисования кнопки

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


Type TOkruglKnopka = class (TModernKnopka)

       procedure Draw; override;

         begin

           RoundRect(x,y,x+Shir,y+Vis,5,5);

           TextOut(x+Round(Shir/2-TextWidth(Nadpis)/2),

                        y+Round(Vis/2-TextHeight(Nadpis)/2),Nadpis)

         end;

     end;   


var Knopka_1:TModernKnopka:=new TOkruglKnopka(1,5,'Кнопка_1');

    Knopka_2:TModernKnopka:=new TOkruglKnopka(80,5,'Кнопка_2');

    Knopka_3:TModernKnopka:=new TOkruglKnopka(160,5,'Кнопка_3');


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


За основу данного кода была взята программа TriKnopky. Здесь приведены только те строчки, которые были изменены или дописаны. Остальной код остался без изменения. Как видите, если бы класс TKnopka был на самом деле уже откомпилирован, то для получения такого же результата без использования полиморфизма пришлось бы почти всю программу писать заново.

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


Абстрактные методы.

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


program Abstractniy_metod;

type TBaz = class

        procedure P_1;

          begin

            P_2;

          end;

        procedure P_2; abstract;

      end;


     TProiz = class (TBaz)

       procedure P_2; override;

         begin

           writeln('Абстрактный метод P_2');

         end;

     end;

var Ob:TBaz:=new TProiz;


begin

  Ob.P_1;

end.

_________________________________________________

Абстрактный метод P_2


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

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


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


Задачи.

1. На основе производного класса TKnopka создать класс потомок TPeremKnopka. Кнопка этого класса должна перемещаться по экрану при нажатой левой кнопки мыши.

2. Описать класс TGeomFig (геометрическая фигура). Объект данного класса должен «уметь» перемещаться по экрану при нажатой левой кнопки мыши. Так же при нажатии на нём левой кнопки мыши, он должен подсвечиваться другим цветом. Метод Draw (метод рисования объекта) сделать абстрактным.

Далее создать производные классы TKrug (круг) и TKvadrat (квадрат), в которых описать переопределённый метод Draw. Во время работы программы случайным образом выбрать геометрическую фигуру и вывести её на экран.

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


Решение.

1.

program PeremeshayushayasyaKnopka;


uses

  GraphABC;


type

  TKnopka = class

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

  end;


type TPeremKnopka = class (TKnopka)//Класс перемещающаяся кнопка

       dx,dy:integer;//Относительные координаты мышки при нажатии

       Naj:boolean:=False;//Равна True, если кнопка нажата

       procedure Move(xx,yy:integer);//Метод перемещает кнопку по экрану

         begin

           SetBrushColor(clWhite);

           FillRect(x,y,x+shir,y+vis);

           x:=xx+dx;

           y:=yy+dy;

           DrawNaj;

         end;

      end;  


var Knopka:TPeremKnopka:=new TPeremKnopka(10,10,'Кнопка');


procedure MouseDown(x,y,mb:integer);

begin

  if mb=1 then

    if Knopka.Najatie(x,y) then

      begin

        Knopka.dx:=Knopka.x-x;

        Knopka.dy:=Knopka.y-y;

        Knopka.Naj:=true;

      end

end;


procedure MouseUp(x,y,mb:integer);

begin

  Knopka.DrawNeNaj;

  Knopka.Naj:=false;

end;


procedure MouseMove(x,y,mb:integer);

begin

  if Knopka.Naj then Knopka.Move(x,y);

end;


begin

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

  OnMouseMove:=MouseMove;

end.


2.

program GeometricheskayaFigura;


uses GraphABC;


type

  TGeomFig = class

    x,y, //Координаты левого верхнего угла фигуры

//Координаты стрелки мыши при нажатии на фигуре относительно х и y

    dx,dy:integer;

    Razm:=100; //Размер фигуры по осям х и y

    Naj:boolean:=false; //True если нажали на фигуре и не отпустили

    constructor (xx,yy:integer);

      begin

        x:=xx; y:=yy;

        DrawNeNaj;

      end;

    procedure Draw; abstract;

    procedure DrawNeNaj;

      begin

        SetBrushColor(clBlue);

        Draw;

      end;

    procedure DrawNaj;

      begin

        SetBrushColor(clGreen);

        Draw;

      end;

    procedure Clear;

      begin

        SetBrushColor(clWhite);

        Inc(Razm,2); Dec(x); Dec(y);

        Draw;

        Dec(Razm,2); Inc(x); Inc(y);

      end;

    //Возвращает True если нажатие произошло на фигуре

    function Najatie(xx,yy:integer):boolean;

      begin

        if (xx>=x) and (xx<=(x+Razm)) and (yy>=y) and (yy<=(y+Razm)) then

           Najatie:=true;

      end;

    procedure Move(xx,yy:integer); //Перемещение фигуры

      begin

        Clear;

        x:=dx+xx;

        y:=dy+yy;

        DrawNaj;

      end;

end;    

     

type TKvadrat = class (TGeomFig)

       procedure Draw; override;

         begin

           FillRect(x,y,x+Razm,y+Razm);

         end;

     end;  


type TKrug = class (TGeomFig)

       procedure Draw; override;

         begin

           FillCircle(x+Round(Razm/2),y+Round(Razm/2),Round(Razm/2));

         end;

     end;  


var Figura:TGeomFig;

     

procedure MouseDown(x,y,mb:integer);

begin

  if Figura.Najatie(x,y) then

    begin

      Figura.dx:=Figura.x-x;

      Figura.dy:=Figura.y-y;

      Figura.Naj:=true;

    end;

end;


procedure MouseUp(x,y,mb:integer);

begin

  Figura.Naj:=false;

  Figura.DrawNeNaj;

end;


procedure MouseMove(x,y,mb:integer);

begin

  if Figura.Naj then Figura.Move(x,y);

end;


begin

  var temp:=Random(1,2);

  case temp of

    1: Figura:=new TKvadrat(10,10);;

    2: Figura:=new TKrug(10,10);;

  end;

  OnMouseDown:=MouseDown;

  OnMouseUp:=MouseUp;

  OnMouseMove:=MouseMove;

end.




Предыдущий параграф Назад в содержание Следующий параграф