|
||||||||
§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.
|