Файл: 8. Переопределение методов. Полиморфные (виртуальные) методы.pdf

Добавлен: 20.10.2018

Просмотров: 1199

Скачиваний: 10

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
background image

 

 

конструктору  и  производится  при  создании  объекта.  Все  объекты-

экземпляры одного класса связаны с одной и той же таблицей. 

Например,  пусть  имеются  два  класса  А  и  В,  в  каждом  из  которых 

объявлено  по  два  виртуальных  метода.  Пусть  при  выполнении  программы 

создано  по  два  объекта  этих  классов.  Тогда  в  памяти  будут  динамически 

выделены  области  для  самих  объектов,  их  таблиц  виртуальных  методов  и 

кода  этих  методов.  Схематично  связи  между  этими  областями  можно 

представить следующим образом.

 

 

 

 

 

 

 

 

 

 

 

 

 

Эта  схема  помогает  понять,  что  происходит  при  выполнении 

программы,  когда  объекты  вызывают  виртуальные  методы.  Пусть 

выполнение  программы  дошло  до  точки,  где  объект  1  класса  А  вызывает 

свой  виртуальный  метод  1.  Тогда  с  помощью  соответствующей  таблицы 

определяется  размещение  в  памяти  кода  этого  метода  и  вместо  заглушки 

происходит  передача  управления  найденному  фрагменту.  Если  же  к 

виртуальному  методу  1  обращается  объект  класса  В,  то  происходит 

обращение  к  другой  таблице  и  вызывается  другой  (альтернативный) 

программный  код.  В  следующем  разделе  эти  общие  концепции 

иллюстрируются на примере иерархии графических объектов. 

объектная  
переменная 1 
класса А 

объектная  
переменная 1 
класса В 

объектная  
переменная 2 
класса А 

объектная  
переменная 2 
класса В 

  значения 
  свойств    
объекта 1 
  класса В 
  адрес ТВМ 
  класса В 

 

  значения 
  свойств                 
объекта 1 
  класса А 
  адрес ТВМ 
  класса А 
 

 

  значения 
  свойств      
объекта 2 
  класса А 
  адрес ТВМ 
  класса А 

 

  значения 
  свойств    
объекта 2 
  класса В 
  адрес ТВМ 
  класса В 

 

  адрес ВМ 1 
  класса А 
  адрес ВМ 2 
  класса А 

 

  адрес ВМ 1 
  класса В 
  адрес ВМ 2 
  класса В 

 

код ВМ 1 класса А 

код ВМ 2 класса А 

код ВМ 1 класса В 

код ВМ 2 класса В 


background image

 

 

В  завершение  данного  раздела  опишем  синтаксические  приемы, 

используемые для объявления виртуальных методов в разных языках. Здесь, 

как  ни  странно,  особняком  стоит  язык  Java.  Дело  в  том,  что  в  языках 

Delphi/FP,  C#,  и  C++  по  умолчанию  методы  считаются  обычными,  не 

переопределяемыми, т.е. обрабатываемыми по схеме раннего связывания. А 

вот в языке Java по умолчанию все методы считаются виртуальными, и 

для них используется механизм динамической компоновки. Это необходимо 

учитывать при объявлении методов в этих языках. 

В языках C#, C++  и  Delphi  для объявления метода переопределяемым 

в родительском классе в его заголовке надо поставить директиву virtual, а в 

дочерних  классах  использовать  либо  директиву  override  (языки  C#    и  

Delphi), либо опять же директиву  virtual (язык C++).   

TParentClass =  class 

     procedure  VirtMethod (параметры);  virtual;  // можно переопределять 

     procedure  Method (параметры);   // обычный (не переопределяемый) 

end; 

 

TChildClass  =  class (TParentClass) 

     procedure   VirtMethod (параметры);  override;  // переопределяем 

     //  метод  Method   просто наследуется и используется без изменений 

     procedure  NewMethod (параметры);   // новый обычный метод 

     procedure  NewVirtMet (параметры);  virtual;  // новый виртуальный 

end; 

 

class   ParentClass  

 {   virtual   void   VirtMethod (параметры);   // можно переопределять 

     void   Method (параметры);   // обычный (не переопределяемый) 

}; 

 


background image

 

 

class   ChildClass :  ParentClass 

 {    override  void   VirtMethod (параметры);   // переопределяем 

     //  метод  Method   просто наследуется и используется без изменений 

     void   NewMethod (параметры);   // новый обычный метод 

     virtual   void   NewVirtMet (параметры);   // новый виртуальный 

 };

 

 

В  отличие  от  этих  языков,  в  Java  отключается  механизм 

переопределения, для чего используется директива   final : 

class   ParentClass  

 {   void   VirtMethod (параметры);   // оставляем как переопределяемый 

     final   void   Method (параметры);   // делаем не переопределяемым 

}; 

 

class   ChildClass  extends  ParentClass 

 {    void   VirtMethod (параметры);   // переопределяем 

     //  метод  Method   просто наследуется и используется без изменений 

     final   void   NewMethod (параметры);   // новый обычный метод 

     void   NewVirtMet (параметры);   // новый виртуальный 

 };

 

 

 

В качестве примера рассмотрим задачу разработки библиотеки классов 

для  графических  объектов  с  использованием  виртуальных  методов.  В 

разделе  7  была  разработана  небольшая  иерархия  классов  для  графических 

объектов  (см.  схему).  В  этой  иерархии  по  вполне  понятным  причинам  не 

использовался  механизм  переопределения  методов.  Теперь  же,  после 

изучения общих принципов переопределения, можно пересмотреть данную 

иерархию с точки зрения использования в ней виртуальных методов.  


background image

 

 

Оставляя  «за  скобками»  конструкторы  и  методы  доступа,  в  числе 

претендентов  на  виртуализацию  остаются  методы  отображения  и 

перемещения,  и  здесь  возникают  достаточно  интересные  моменты.  Метод 

отображения  уникален  для  каждого  класса  и  поэтому  в  каждом  классе 

должен  быть  реализован  своим  программным  кодом.  А  вот  метод 

перемещения  алгоритмически  одинаков:  перемещение  любой  фигуры 

можно описать тремя основными шагами: 

  стираем старое изображение за счет вызова метода прорисовки 

  изменяем координаты базовой точки фигуры 

  снова вызываем метод прорисовки 

Ну  а  поскольку  алгоритм  перемещения  одинаков,  то  зачем  в  каждом 

классе  повторять  программный  код  метода  перемещения?  Разумно 

выполнить  реализацию  этого  метода  один  раз  –  в  абстрактном  классе 

фигур  и  далее  наследовать  его  во  всех  производных  классах.  Напомним, 

что  понятие  абстрактного  класса  не  запрещает  включать  в  него 

программный  код  некоторых  методов.  Следовательно,  метод  перемещения 

не имеет смысла делать переопределяемым. 

Идем  дальше.  Мы  решили  реализовать  метод  перемещения  на  «самом 

верху»  -  в  классе  фигур.  Но  для  программной  реализации  этого  метода 

необходимо вызывать метод отображения, а вот его в классе фигур  нельзя 

реализовать в принципе, можно включить в класс лишь его заголовок! Раз 

нельзя  –  так  нельзя,  но  заголовок-то  метода  есть,  вот  и  будем  в  методе 

перемещения «как бы вызывать» метод отображения, используя только его 

заголовок!  Тем  самым  метод  перемещения  оформляется  как  некая 

заготовка  настоящего  полноценного  метода,  а  для  того  чтобы  все  это 

заработало,  метод  прорисовки  надо  объявить  виртуальным  и 

переопределять в каждом производном классе. 

В  этом  случае  при  обработке  класса  фигур  компилятор/компоновщик 

для метода перемещения создаст лишь часть программного кода, поскольку 

в  точках  вызова  метода  отображения  вместо  реальных  команд  будут 


background image

 

 

вставлены  заглушки.  При  обработке  дочерних  классов,  в  которых 

переопределены методы прорисовки, компилятор/компоновщик для каждого 

класса  создаст  таблицу  виртуальных  методов,  которая  во  время 

выполнения  программы  способна  хранить  адрес  размещения  в  памяти 

программного кода конкретной версии метода отображения. Это позволит 

во  время  работы  программы  динамически  настраивать  метод 

перемещения  на  работу  с  конкретным  объектом,  подставляя  вместо 

заглушек  необходимый  код  отображения.  Это  и  есть  позднее  связывание: 

имя  метода  (в  данном  случае  –  метода  отображения)  заменяется 

программным кодом лишь во время работы программы! 

Теперь  становится  понятным,  почему  во  всех  классах  методы 

отображения должны иметь одинаковые имена (неважно – Show, Draw или 

как-то  еще,  главное  –  одинаково!).  Напомним  еще  раз,  что  одноименность 

методов  является  обязательным  условием  использования  механизма 

переопределения. Это и есть полиморфизм методов – имя одно, а вариантов 

программной реализации много (ну хотя бы два :)). 

Итак,  в  иерархию  классов  для  графических  объектов  надо  внести 

следующие изменения: 

  в 

базовом 

классе 

фигур 

метод 

отображения 

объявляем 

виртуальным, оставляя его абстрактным 

  здесь  же  метод  перемещения  объявляем  уже  НЕ  абстрактным,  и 

следовательно  даем  ему  программную  реализацию,  в  которую 

вставляем вызовы абстрактного виртуального метода отображения 

  во  всех  производных  классах  убираем  методы  перемещения

поскольку  этот  метод  теперь  может  наследоваться  из  корневого 

класса  иерархии  и  с  помощью  механизма  позднего  связывания 

динамически настраиваться на перемещение объекта любого класса 

  во  всех  производных  классах  переопределяем  метод  отображения  и 

даем ему конкретную программную реализацию с учетом специфики 

объектов данного класса