PascalABC.NET

Виртуальные методы и полиморфизм

Полиморфизм (от греч. "много форм") - это свойство классов, связанных наследованием, иметь различную реализацию входящих в них методов, и способность переменной базового класса вызывать методы того класса, объект которого содержится в этой переменной в момент вызова метода.

Полиморфизм используется в ситуации, когда для группы взаимосвязанных объектов требуется выполнить единое действие, но каждый из этих объектов должен выполнить указанное действие по-своему (т.е. у действия возникает много форм). Для этого определяется базовый для всех объектов класс с виртуальными методами, предусмотренными для меняющегося поведения, после чего эти методы переопределяется в потомках.

Для пояснения рассмотрим переопределение метода в подклассе:

type   Base = class
  public

    procedure Print;
    begin
      writeln('Base');
    end;
  end;
  Derived = class(Base)
  public
    procedure Print;
    begin
      writeln('Derived');
    end;
  end;

Присвоим переменной базового класса Base объект производного класса Derived и вызовем метод Print.

var b: Base := new Derived;
b.Print;

Какая версия метода Print вызывается - класса Base или класса Derived? В данном случае решение будет принято еще на этапе компиляции: вызовется метод Print класса Base, заявленного при описании переменной b. Говорят, что имеет место раннее связывание имени метода с его телом. Если же решение о том, какой метод вызывать, принимается на этапе выполнения программы в зависимости от реального типа объекта, на который ссылается переменная b, то в данном случае вызывается метод Derived.Print (говорят также, что имеет место позднее связывание). Методы, для которых реализуется позднее связывание, называются виртуальными, а переменная базового класса, через которую осуществляется вызов виртуального метода, - полиморфной переменной. Таким образом, полиморфизм реализуется вызовом виртуальных функций через переменную базового класса. Тип класса, который хранится в данной переменной на этапе выполнения, называется динамическим типом этой переменной.

Для того чтобы сделать метод виртуальным, следует в объявлении этого метода после заголовка указать ключевое слово virtual с последующей ;. Для переопределения виртуального метода следует использовать ключевое слово override:

type
  Base = class
  public

    procedure Print; virtual;
    begin
      writeln('Base');
    end;
  end;
  Derived = class(Base)
  public
    procedure Print; override;
    begin
      writeln('Derived');
    end;
  end;

Теперь в аналогичном участке кода.

var b: Base := new Derived;
b.Print;

вызывается метод Print класса Derived за счет того что решение о вызове метода откладывается на этап выполнения программы.

Говорят, что методы Print завязаны в цепочку виртуальности. Чтобы разорвать ее (не вызывать методы в подклассах виртуально) используется ключевое слово reintroduce:

type
  DerivedTwice1 = class(Derived)
  public
    procedure Print;
reintroduce;
    begin
      writeln('DerivedTwice1');
    end;
  end;

Если мы хотим начать новую цепочку виртуальности, то следует использовать и virtual и reintroduce:

type
  DerivedTwice2 = class(Derived)
  public
    procedure Print;
virtual; reintroduce;
    begin
      writeln('DerivedTwice2');
    end;
  end;

Если переопределить виртуальную функцию невиртуальной без ключевого слова reintroduce, то ошибки не произойдет - будет выведено лишь предупреждение о том, что цепочка виртуальности нарушена. Таким образом, ключевое слово reintroduce в этой ситуации лишь подавляет вывод предупреждения.

При переопределении виртуального метода в подклассе его уровень доступа должен быть не ниже, чем в базовом классе. Например, public виртуальный метод не может быть переопределен в подклассе private-методом.