#29 Переопределение методов классов в Паскале. ООП
Primary tabs
Переопределение метода - возможность в поддерживающих ООП языках программирования изменить поведение метода из наследуемого родительского класса в классе-потомке
Рассмотрим пример:
type Cat = class // родительский класс public constructor create(nameValue: string); procedure sayHello(); strict protected name: string; end; Tiger = class(Cat) // класс-потомок end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.name + '!'); end; var CatItem: Cat; TigerItem: Tiger; begin CatItem := Cat.create('Мурка'); CatItem.sayHello(); TigerItem := Tiger.create('Шархан'); TigerItem.sayHello(); end.
-- что если мы хотим, чтобы все тигры при приветствии (в отличии от кошек) добавляли перед своим имененем слово "тигр", этого можно добавить переопределением метода sayHello()
type Cat = class // родительский класс public constructor create(nameValue: string); procedure sayHello(); strict protected name: string; end; Tiger = class(Cat) // класс-потомок procedure sayHello(); // переопределяемый метод end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.name + '!'); end; // реализация переопределяемого в потомке метода procedure Tiger.sayHello(); begin writeln('Привет, я тигр ' + self.name + '!'); end; var CatItem: Cat; TigerItem: Tiger; begin CatItem := Cat.create('Мурка'); CatItem.sayHello(); TigerItem := Tiger.create('Шархан'); TigerItem.sayHello(); end.
-- таким образом:
мы реализовали в классе Tiger собственную версию процедуры sayHello(), переопределив ее поведение
клиентский код остался прежним (вызываем метод с тем же именем как и раньше), но теперь выполняется код не из родительского класса, а из класса-потомка
Таким образом: при вызове перепределяемого метода в объекте какого-либо класса используется ближайшая реализация в цепочке классов-предков.
Это значит, что:
- если реализация есть в самом классе - то она берется из него
- если в ннепосредственном родителе (как в примере выше), то из родителя
- если в "классе-дедушке" (т.е. в родителе родительского класса) -то его
Если же реализации метода нет нигде в цепочке наследования, то Паскаль выбросит ошибку на этапе анализа синтаксиса программы.
Несколько элементов в цепочке наследования
Как уже было сказано выше, в цепочке наследования для класса, может быть более 2 элементов, напр. ниже их три:
type Cat = class // базовый родительский класс public constructor create(nameValue: string); procedure sayHello(); strict protected name: string; end; Tiger = class(Cat) // класс-потомок Cat public procedure sayHello(); end; AmurTiger = class(Tiger) // класс-потомок Tiger procedure sayHello(); end; constructor Cat.create(nameValue: string); begin self.name := nameValue; end; procedure Cat.sayHello(); begin writeln('Привет, я ' + self.name + '!'); end; procedure Tiger.sayHello(); begin writeln('Привет, я тигр ' + self.name + '!'); end; procedure AmurTiger.sayHello(); begin writeln('Привет, я амурский тигр ' + self.name + '!'); end; var CatItem, MyCat: Cat; TigerItem: Tiger; MyTiger: AmurTiger; begin CatItem := Cat.create('Мурка'); CatItem.sayHello(); MyCat := Cat.create('Мурзик'); MyCat.sayHello(); TigerItem := Tiger.create('Шархан'); TigerItem.sayHello(); MyTiger := AmurTiger.create('Шархан2'); MyTiger.sayHello(); end.
-- тут Амурский Тигр наследуется от класс Тигр, который в свою очередь наследуется от класса "Кошка" (Cat).
Пример №2 -- Переопределение метода при непрямом (косвенном) вызове
Рассмотрим пример-проблему:
type Animal = class public constructor create(skinValue: char); function getValue(): integer; function getValueWrapper(): integer; // "обертка" над getValue() protected skin: char; end; AnimalNext = class(Animal) // класс-наследник function getValue(): integer; // переопределяет 1 функцию end; constructor Animal.create(skinValue: char); begin self.skin := skinValue; end; function Animal.getValueWrapper(): integer; begin result := self.getValue(); end; function Animal.getValue(): integer; begin result := 4; end; // переопределяем функцию - с той же сигнатурой function AnimalNext.getValue(): integer; begin result := 5; // меняем на 5 end; var AnimalNextItem: AnimalNext; AnimalItem: Animal; begin AnimalNextItem := AnimalNext.create('*'); AnimalItem := Animal.create('*'); writeln( 'test - ', // вызов собственной функции класса данного объекта AnimalItem.getValueWrapper(), // вернет 4 ' ', // вызов унаследованной функции данного объекта AnimalNextItem.getValueWrapper() // вернет тоже 4 (а ожидали 5!) ); // вызовем метод getValue() напрямую writeln( 'test2 - ', // вызов собственной функции класса данного объекта AnimalItem.getValue(), // вернет 4 ' ', // вызов собственной функции класса данного объекта AnimalNextItem.getValue() // вернет тоже 4 ); readln(); end.
Для переопределения методов даже для непрямого вызова во Free Pascal используются ключевые слова-метки:
- virtual - указание в т.ч. на то, что класс метод может быть помечен как override в классах-потомках (т.е. как раз там где переопределяется)
- override -- указание на то, что данный метод будет переопределять родительский
Последий пример с использованием этим меток можно переписать так:
type Animal = class public constructor create(skinValue: char); function getValue(): integer; virtual; function getValueWrapper(): integer; // "обертка" над getValue() protected skin: char; end; AnimalNext = class(Animal) // класс-наследник function getValue(): integer; override; // переопределяет 1 функцию end; constructor Animal.create(skinValue: char); begin self.skin := skinValue; end; function Animal.getValueWrapper(): integer; begin result := self.getValue(); end; function Animal.getValue(): integer; begin result := 4; end; // переопределяем функцию - с той же сигнатурой function AnimalNext.getValue(): integer; begin result := 5; // меняем на 5 end; var AnimalNextItem: AnimalNext; AnimalItem: Animal; begin AnimalNextItem := AnimalNext.create('*'); AnimalItem := Animal.create('*'); writeln( 'test - ', // вызов собственной функции класса данного объекта AnimalItem.getValueWrapper(), // вернет 4 ' ', // вызов унаследованной функции данного объекта AnimalNextItem.getValueWrapper() // вернет 5, как и ожидалось! ); readln(); end.
-- для лучшего понимания см. видео-разбор.
Пример №3 - Обращение к родительскому методу
Во Free Pascal можно обращаться к реализации того же метода в родителе (см. официальную документацию) с помощью ключевого слова Inherited.
Пример-проблема:
type Animal = class public constructor create(skinValue: char); function getSkin(): char; protected skin: char; end; AnimalNext = class(Animal) // класс-наследник constructor create(skinValue: char); // переопределяем конструктор end; constructor Animal.create(skinValue: char); begin self.skin := skinValue; end; function Animal.getSkin(): char; begin result := self.skin; end; // переопределяем функцию - с той же сигнатурой constructor AnimalNext.create(skinValue: char); begin writeln('Gotovo!'); end; var AnimalItem: Animal; AnimalNextItem: AnimalNext; begin AnimalItem := Animal.create('*'); AnimalNextItem := AnimalNext.create('#'); // не установится! writeln('test [', AnimalItem.getSkin(), '] [', AnimalNextItem.getSkin(), ']'); readln(); end.
-- нам нужно, чтобы передаваемое в конструструктор значение, записывалось в поле класса, это можно сделать с помощью Inhireted:
type Animal = class public constructor create(skinValue: char); function getSkin(): char; protected skin: char; end; AnimalNext = class(Animal) // класс-наследник constructor create(skinValue: char); // переопределяем конструктор end; constructor Animal.create(skinValue: char); begin self.skin := skinValue; end; function Animal.getSkin(): char; begin result := self.skin; end; // переопределяем функцию - с той же сигнатурой constructor AnimalNext.create(skinValue: char); begin Inherited create(skinValue); // сначала вызываем конструктор родителя! writeln('Gotovo!'); end; var AnimalItem: Animal; AnimalNextItem: AnimalNext; begin AnimalItem := Animal.create('*'); AnimalNextItem := AnimalNext.create('#'); // установится как надо! writeln('test [', AnimalItem.getSkin(), '] [', AnimalNextItem.getSkin(), ']'); readln(); end.
-- для лучшего понимания см. видео-разбор.
Видео-материалы
- Переопределение методов в классах. Введение: https://youtu.be/bRV-L1TNMlI
Задачи для самостоятельного решения
- У вас есть код, который создает сцену, которой можно управлять нажимая клавиши '1' '2' (см. первый урок практики по ООП):
uses Crt; // импортираем модуль Crt type Animal = class public constructor create(skinValue: char); procedure drawFrame(); procedure handleCommand(); function getCommand(): char; protected skin: char; // внешний вид position: integer; // текущая позиция command: char; // последняя введенная команда function getNewPosition(commandValue: char): integer; end; constructor Animal.create(skinValue: char); begin self.skin := skinValue; self.command := ' '; // значение по умолчанию end; procedure Animal.drawFrame(); var i: integer; begin for i:=1 to (self.position-1) do write(' '); writeln(self.skin); end; procedure Animal.handleCommand(); begin self.command := ReadKey(); // вместо readln(command) self.position := self.getNewPosition(self.command); end; function Animal.getNewPosition(commandValue: char): integer; begin if (commandValue = '2') then result := self.position + 1 else if (position > 1) then result := self.position - 1; end; function Animal.getCommand(): char; begin result := self.command; end; var AnimalItem: Animal; begin AnimalItem := Animal.create('*'); while(AnimalItem.getCommand() <> 'q') do begin ClrScr(); AnimalItem.drawFrame(); Delay(500); if (keyPressed()) then AnimalItem.handleCommand(); end; writeln('Programma zavershena!'); readln(); end.
-- создайте класс AnimalNext, унаследованный от Animal и с помощью переопределения метода сделайте так. чтобы его объект перемещался влево и вправо сразу на 2 пробельный символа при нажатии управляющих клавишь (а не на 1 символ, как случае с исходным классом Animal).
-
Есть код, который позволяет управлять сразу двумя объектами в анимационной сцене:
uses Crt; // импортираем модуль Crt type Animal = class public constructor create(skinValue, moveLeftChar, moveRightChar: char); procedure drawFrame(); procedure handleCommand(commandValue: char); function getCommand(): char; function getPosition(): integer; function isCurrentPosition(position: integer): boolean; protected moveLeftChar: char; // клавиша (символ) для движения влево moveRightChar: char; // клавиша (символ) для движения вправо skin: char; // внешний вид position: integer; // текущая позиция command: char; // последняя введенная команда function getNewPosition(commandValue: char): integer; end; Scene = class // Сцена Анимации public procedure handleCommand(); procedure run(AnimalItem, SecondAnimal: Animal); procedure drawSceneFrame(AnimalItem, SecondAnimal: Animal); function getCommand(): char; protected command: char; // последняя введенная команда function getMax(a, b: integer): integer; end; // -- Реализация Scene ------- procedure Scene.run(AnimalItem, SecondAnimal: Animal); begin // обработки событий while(self.getCommand() <> 'q') do begin ClrScr(); // новый метод, для отрисовки кадра и всех объектов self.drawSceneFrame(AnimalItem, SecondAnimal); writeln(); Delay(50); if (keyPressed()) then begin self.handleCommand(); AnimalItem.handleCommand(self.getCommand()); SecondAnimal.handleCommand(self.getCommand()); end; end; writeln('Scena zavershena!'); end; // Отрисовка кадра сцены procedure Scene.drawSceneFrame(AnimalItem, SecondAnimal: Animal); var maxPosition, i: integer; begin maxPosition := self.getMax(AnimalItem.getPosition(), SecondAnimal.getPosition()); for i:=1 to maxPosition do begin if (AnimalItem.isCurrentPosition(i)) OR (SecondAnimal.isCurrentPosition(i)) then begin if (AnimalItem.isCurrentPosition(i)) then AnimalItem.drawFrame(); if (SecondAnimal.isCurrentPosition(i)) then SecondAnimal.drawFrame(); end else write(' '); end; end; function Scene.getMax(a, b: integer): integer; begin if (a > b) then result := a else result := b; end; procedure Scene.handleCommand(); begin self.command := ReadKey(); // читаем символ из консоли end; function Scene.getCommand(): char; begin result := self.command; end; // -- Реализация Animal ------- constructor Animal.create(skinValue, moveLeftChar, moveRightChar: char); begin self.skin := skinValue; self.moveLeftChar := moveLeftChar; self.moveRightChar := moveRightChar; self.command := ' '; // значение по умолчанию self.position := 1; end; procedure Animal.drawFrame(); begin write(self.skin); end; // больше не работаем с консолью напрямую procedure Animal.handleCommand(commandValue: char); begin self.position := self.getNewPosition(commandValue); end; function Animal.getNewPosition(commandValue: char): integer; begin result := self.position; // предыдущее значение, как значение по умолчанию if (commandValue = self.moveRightChar) then result := self.position + 1 else if ((commandValue = self.moveLeftChar) AND (self.position > 1)) then result := self.position - 1; end; function Animal.getCommand(): char; begin result := self.command; end; function Animal.getPosition(): integer; begin result := self.position; end; function Animal.isCurrentPosition(position: integer): boolean; begin result := (position = self.position); end; // --------------- var AnimalItem, // первое животное Dog // второе животное : Animal; SceneItem: Scene; begin AnimalItem := Animal.create('*', '1', '2'); Dog := Animal.create('@', '3', '4'); SceneItem := Scene.create(); // создаем сцену SceneItem.run(AnimalItem, Dog); // запускаем анимацию readln(); end.
-- перепроектируйте классы так, чтобы можно было двигаться на плоскости (в двумерном, а не одномерном пространстве):
- базовый класс для животного оставьте без изменения, для перемешения в двумерном пространстве создайте два класса-потомка от Animal:
- класс D2Animal, объекты которого двигаются по плоскости
- класс D1Animal, объекты которого могут двигаться только по первой строчке (как и Animal, новый класс тут нужен, чтобы обобщить работу с координатами)
- Добавьте класс D2Scene для работы с новым двумерным пространством
- базовый класс для животного оставьте без изменения, для перемешения в двумерном пространстве создайте два класса-потомка от Animal:
- Log in to post comments
- 2232 reads
vedro-compota
Sat, 03/11/2023 - 12:22
Permalink
type
_____________
матфак вгу и остальная классика =)
slavina036
Sun, 04/30/2023 - 16:13
Permalink
uses Crt; // импортираем