Урок 64 - Виртуальные методы, полиморфизм, абстрактные классы, (часть 6/6)

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

  Все это мы видели на примерах в предыдущем разделе. Но представим себе такую задачу. Вы объявили в приложении массив объектов типа TPerson:
var PersArray: array[1..10] of TPerson;

  Далее заполняете этот массив вперемешку объектами классов TStudent и TEmpl, т.е. создаете, например, общий список учащихся и преподавателей. В разд. 3.5.4 вы видели, что это возможно, так как переменная базового класса может принимать объекты производных классов. А затем хотите пройти в цикле элементы этого массива и отобразить в окне Memo информацию о них:

for i:=l to 10 do Memol.Lines.Add(PersArray[i].PersonToStr);

  Или аналогичная задача с использованием списка TList:
объявлена переменная var List: TList; в нее заносятся указатели на объекты классов TStudent и TEmpl, а затем вы хотите пройти в цикле элементы этого списка и отобразить их в окне Memo:

for i:=0 to List.Count - 1 do Memol.Lines.Add(TPerson(List[i]).PersonToStr);

  Реализуйте один из этих вариантов (не забудьте при завершении приложения удалять объекты из памяти), и посмотрите, что получится. А получится очевидный результат: во всех случаях сработает метод PersonToStr родительского класса TPerson, так как соответствующие объекты объявлены объектами этого класса. И программа, естественно, не знает, объекты какого класса хранятся в массиве или в списке.

  Конечно, можно усложнить код, проверять каждый раз операцией is истинный класс объекта, и указывать операцией as этот класс (см. об этих операциях в разд. 3.5.4). Для массива это будет выглядеть так:

if PersArrayfi] is TStudent then 
 Memol.Lines.Add((PersArray[i] as TStudent).PersonToStr)
  else 
if PersArray[i] is TEmpl then 
 Memol.Lines.Add((PersArray[i] as TEmpl).PersonToStr);

  А для списка аналогичный код имеет вид:


if  TPerson(List[i])   is  TStudent then    Memol.Lines.Add(TStudent(List[i]).PersonToStr) 
 else  
 if  TPerson(List[i]) is TEmpl then    Memol.Lines.Add(TEmpl(List[i]).PersonToStr);

  Задача будет решена, но некрасиво. Во-первых, код усложняется. А во-вторых, если вы впоследствии решите создать какие-то новые классы, производные от TPerson, вам придется вводить соответствующие дополнительные проверки в эти коды.

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

Memol.Lines.Add(PersArray[i].PersonToStr); Memol.Lines.Add(TPerson(List[i]).PersonToStr);

  будут автоматически вызывать методы PersonToStr того класса (TStudent, TEmpl или других производных от TPerson), к которому относится каждый объект. Такой подход, облегчающий работу с множеством родственных объектов, называется полиморфизмом.

   Сделать метод родительского класса виртуальным очень просто. При объявлении в классе виртуальных методов после точки с занятой, завершающей объявление метода, добавляется ключевое слово virtual. Например, чтобы объявить в базовом классе TPerson метод PersonToStr виртуальным, надо в его объявление добавить слово virtual:

function PersonToStr: string; virtual;

  Чтобы перегрузить в классе-наследнике виртуальный метод, надо после его объявления поставит ключевое слово override. В наших примерах объявления метода в классах и должны выглядеть так:

function PersonToStr: string; override;
И это все! Методы стали виртуальными и приведенные в начале данного раздела операторы, будут работать безо всяких проверок if.

  Если в каком-то базовом классе метод был объявлен как виртуальный, то он остается виртуальным во всех классах-наследниках (в частности, и в наследниках классов наследников). Однако обычно для облегчения понимания кодов, перегруженные методы принято повторно объявлять виртуальными, чтобы была ясна их суть для тех, кто будет строить наследников данного класса. Например:

function PersonToStr: string; override; virtual;

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

  Объявляется абстрактный метод с помощью ключевого слова abstract после слова virtual. Например, вы можете в классе TPerson объявить метод PersonToStr следующим образом:

function PersonToStr: string; virtual; abstract;

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

Удачи!
Встретимся в следующем уроке!




Добавил(а): Programer Дата: 2013-08-04
Эй уже кто-нибудь ответит мне ?


  1. sendsay
    sendsay a year ago
    Сжато и поделу. Спасибо.
  2. Сергей
    Сергей a year ago
    Было бы уместно добавить ссылки на след. и пред. статью в конце этой статьи.
Отменить.