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

Добавлен: 20.10.2018

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

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

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

 

 

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

Переопределение  методов  является  одним  из  проявлений  так 

называемого полиморфизма - важного и, в то же время, самого трудного для 

понимания  принципа  объектной  технологии.  Дословный  перевод  термина 

«полиморфный» – это «имеющий много форм», когда одна и та же сущность 

в разных ситуациях может по-разному проявлять себя.  

Одним  из  самых  простых  проявлений  полиморфизма  можно  считать 

возможность  перегрузки  (overloading)  методов  в  классе,  т.е.  наличие  в 

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

формальных  параметров  и  разной  программной  реализацией.  Например, 

перегруженные конструкторы класса (напомним, что в языках Java, C# и C++ 

ситуация  перегрузки  конструкторов  возникает  автоматически  при 

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

формы  реализации  одной  и  той  же  сущности  –  метода,  отвечающего  за 

создание и инициализацию объекта.  

Перейдем  к  подробному  рассмотрению  другого  проявления 

полиморфизма  применительно  к  сущностям-методам,  а  именно  –  к 

переопределению методов. При этом надо ответить на следующие вопросы: 

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

  что лежит в основе программной реализации этого механизма 

 

Начнем с первого достаточно простого вопроса. 

Переопределение (overriding) методов 

 – это возможность объявления в дочерних классах методов,  

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

методом, но этим методам в дочерних классах дается своя 

программная реализация

 

 

Несколько важных замечаний: 


background image

 

 

  использование 

механизма 

переопределения 

не 

является 

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

достаточно  функциональные  объектные  программы  без  этого 

механизма 

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

него  оно  не  имеет  смысла,  что  еще  раз  подчеркивает  центральное 

место,  которое  в  объектной  технологии  отводится  принципу 

наследования 

  полное  совпадение  заголовков  включает  в  себя  совпадение  имен 

методов  (вплоть  до  регистра  для  С-подобных  программ!),  числа

порядка и типов формальных параметров, если они есть (этот набор 

атрибутов подпрограмм часто называют сигнатурой) 

  переопределяемые методы принято называть виртуальными 

 

В  результате  в  рамках  некоторой  подиерархии  классов  (а  это  хотя  бы 

два  класса  –  родительский  и  дочерний)  появляются  одноименные 

(правильнее сказать – односигнатурные) методы, т.е. методы с одинаковой 

сигнатурой  (имя  +  параметры)  но  с  разным  программным  кодом.  Тем 

самым реализуется следующая интересная возможность: 

 

В дочерних классах можно видоизменять, модифицировать 

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

особенностям объектов конкретных классов. 

В целом это позволяет создавать более гибкие объектные 

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

выполнения в зависимости от текущей ситуации.

 

 

Здесь ОЧЕНЬ важно подчеркнуть следующее: в объектной программе, 

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

вариантов  реализации  одного  и  того  же  виртуального  метода 


background image

 

 

(многоформность,  однако!),  и  решение  о  том  какой  из  них  вызывать 

принимается  во  время  выполнения  программы  в  зависимости  от  того

объекту  какого  класса  требуется  этот  метод.  Отсюда  следует,  что  часть 

работы  по  связыванию  программного  кода  переносится  с  этапа 

компиляции/компоновки  на  этап  выполнения.  Поскольку  связывание 

программного кода во время выполнения программы требует определенных 

затрат,  то  при  проектировании  класса  возникает  вопрос  о  соотношении 

переопределяемых и не переопределяемых (т.е. обычных) методов. Ответ на 

этот вопрос сильно зависит от специфики решаемой задачи и часто является 

весьма непростым. 

Разобьем этот вопрос на два подвопроса: 

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

(если это вообще разрешено родителем) и какие именно 

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

имеет  смысл  разрешить  к  переопределению  в  последующих 

производных классах 

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

сигнатура  родительского  метода  (или  нескольких  методов),  в  заголовке 

метода  он  описывается  как  переопределяемый  (виртуальный)  с  помощью 

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

отличающаяся от родительской). 

Во втором случае метод(ы) также в заголовке помечается специальным 

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

реализация, либо (достаточно часто) он объявляется абстрактным

В итоге, в самом общем случае проектируемый класс может содержать 

следующие разновидности методов: 

  обычные  унаследованные  методы,  которые  присутствуют  в  классе 

неявно 

  переопределенные унаследованные методы (виртуальные) 

  вновь введенные в классе обычные невиртуальные методы 


background image

 

 

  вновь  введенные  в  классе  методы,  разрешенные  к  переопределению, 

т.е. объявленные виртуальными  

 

Вся  эта  информация  о  методах необходима  для правильной обработки 

описания  класса  компилятором/компоновщиком.  При  этом  обычные  и 

виртуальные методы обрабатываются по-разному.  

Для обычных методов используется схема  раннего связывания (early 

binding,  статическая  компоновка),  когда  замена  (связывание)  имени 

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

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

программный  код,  в  котором  все  адресные  связи  настроены  необходимым 

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

быстро.  Но  с  другой  стороны,  такая  жесткая  фиксация  адресных  связей  не 

позволяет менять траекторию выполнения программы. 

Для виртуальных методов используется другая схема – так называемое 

позднее 

связывание 

или 

динамическая 

компоновка

Здесь 

компилятор/компоновщик  выполняет  лишь  некоторую  подготовительную 

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

кодом  происходит  при  работе  программы.  В  этом  случае  создаваемый 

программный  код  имеет  «полуфабрикатный»  вид,  в  нем  отсутствуют 

некоторые  необходимые  программные  фрагменты,  что  позволяет  при 

выполнении  программы  в  одной  и  той  же  ее  точке  динамически 

подключать  разные  фрагменты  программного  кода.  Это  безусловно 

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

выполнение. 

Эти факторы обязательно надо учитывать при проектировании классов 

и  решении  вопроса  о  степени  использования  виртуальных  методов.  Если  в 

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

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

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


background image

 

 

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

но  время  выполнения  –  больше.  На  практике  обычно  стремятся  найти 

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

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

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

закрытым  свойствам  класса.  Также  виртуальными  не  объявляют 

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

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

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

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

открывает  целый  ряд  интересных  возможностей,  которые  описываются  в 

следующих разделах пособия. 

Теперь  немного  поговорим  о  тех  внутренних  механизмах,  на  которых 

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

методы,  то  компилятор/компоновщик  в  точках  вызова  этих  методов 

формирует  специальные  конструкции,  часто  называемые  «заглушками». 

Кроме того, создаются специальные структуры данных с информацией об 

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

называют  Таблицами  Виртуальных  Методов  (VMT,  Virtual  Method 

Table).  В  разных  языках  эти  таблицы  реализованы  немного  по-разному,  но 

общий  принцип  их  использования  одинаков:  предоставить  механизму 

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

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

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

выполнения программы находится в оперативной памяти и содержит адреса 

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

доступа  к  этим  таблицам  у  каждого  объекта  есть  внутреннее  скрытое  от 

разработчика  поле,  которое  используется  для  хранения  адреса 

соответствующей  таблицы.  Другими  словами,  каждый  объект  связан  со 

своей  таблицей  виртуальных  методов.  Заполнение  этого  поля  поручено