#29 Переопределение методов классов в Паскале. ООП

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

Рассмотрим пример:

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.

-- для лучшего понимания см. видео-разбор.

Видео-материалы

Задачи для самостоятельного решения

  1. У вас есть код, который создает сцену, которой можно управлять нажимая клавиши '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).

  2. Есть код, который позволяет управлять сразу двумя объектами в анимационной сцене:
    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:
      1. класс D2Animal, объекты которого двигаются по плоскости
      2. класс D1Animal, объекты которого могут двигаться только по первой строчке (как и Animal, новый класс тут нужен, чтобы обобщить работу с координатами)
    • Добавьте класс D2Scene для работы с новым двумерным пространством
vedro-compota's picture

type
  Cat = class  // родительский класс
  public


    constructor create(nameValue: string);
    procedure sayHello();

  strict protected
    name: string;
  end;

  Tiger = class(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.

// -----------------


type
  Human = class  // родительский класс
  public
    procedure sayHello();
    procedure sayHello(name: string);
    procedure sayHello(name, surname: string);
  end;

procedure Human.sayHello();
begin
 writeln('Привет, я человек!');
end;

procedure Human.sayHello(name: string);
begin
 writeln('Привет, я ' + name + '!');
end;

procedure Human.sayHello(name, surname: string);
begin
 writeln('Привет, я ', name, ' ', surname, '!');
end;

var
  Man: Human;
begin
  Man := Human.create();
  Man.sayHello();
  Man.sayHello('Вася');
  Man.sayHello('Вася', 'Петров');
end.

_____________
матфак вгу и остальная классика =)

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; virtual;
  end;

   AnimalNext = class(Animal)
     function getNewPosition(commandValue: char): integer; override;
   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;

// реализация переопределяемого в потомке метода
function AnimalNext.getNewPosition(commandValue: char): integer;
begin
  if (commandValue = '2') then
    result := self.position + 5
  else
    if (position > 1) then
      result := self.position - 5;
end;


var
  AnimalItem: AnimalNext;
begin
  AnimalItem := AnimalNext.create('*');
  while(AnimalItem.getCommand() <> 'q') do
  begin
    ClrScr();
    AnimalItem.drawFrame();
    Delay(500);
    if (keyPressed()) then
       AnimalItem.handleCommand();
  end;

  writeln('Programma zavershena!');
  readln();
end.