Файл: 8. Переопределение методов. Полиморфные (виртуальные) методы.pdf
ВУЗ: Университет управления «ТИСБИ»
Категория: Учебное пособие
Дисциплина: Объектно-ориентированное программирование
Добавлен: 20.10.2018
Просмотров: 1350
Скачиваний: 10
8. Переопределение методов. Полиморфные (виртуальные) методы
Переопределение методов является одним из проявлений так
называемого полиморфизма - важного и, в то же время, самого трудного для
понимания принципа объектной технологии. Дословный перевод термина
«полиморфный» – это «имеющий много форм», когда одна и та же сущность
в разных ситуациях может по-разному проявлять себя.
Одним из самых простых проявлений полиморфизма можно считать
возможность перегрузки (overloading) методов в классе, т.е. наличие в
классе нескольких методов с одним именем, но разным набором
формальных параметров и разной программной реализацией. Например,
перегруженные конструкторы класса (напомним, что в языках Java, C# и C++
ситуация перегрузки конструкторов возникает автоматически при
объявлении более одного конструктора) можно рассматривать как разные
формы реализации одной и той же сущности – метода, отвечающего за
создание и инициализацию объекта.
Перейдем к подробному рассмотрению другого проявления
полиморфизма применительно к сущностям-методам, а именно – к
переопределению методов. При этом надо ответить на следующие вопросы:
в чем смысл механизма переопределения и что он дает
что лежит в основе программной реализации этого механизма
Начнем с первого достаточно простого вопроса.
Переопределение (overriding) методов
– это возможность объявления в дочерних классах методов,
заголовки которых полностью совпадают с базовым родительским
методом, но этим методам в дочерних классах дается своя
программная реализация
Несколько важных замечаний:
использование
механизма
переопределения
не
является
обязательным во всех объектных программах, можно создавать
достаточно функциональные объектные программы без этого
механизма
переопределение неразрывно связано с механизмом наследования, без
него оно не имеет смысла, что еще раз подчеркивает центральное
место, которое в объектной технологии отводится принципу
наследования
полное совпадение заголовков включает в себя совпадение имен
методов (вплоть до регистра для С-подобных программ!), числа,
порядка и типов формальных параметров, если они есть (этот набор
атрибутов подпрограмм часто называют сигнатурой)
переопределяемые методы принято называть виртуальными
В результате в рамках некоторой подиерархии классов (а это хотя бы
два класса – родительский и дочерний) появляются одноименные
(правильнее сказать – односигнатурные) методы, т.е. методы с одинаковой
сигнатурой (имя + параметры) но с разным программным кодом. Тем
самым реализуется следующая интересная возможность:
В дочерних классах можно видоизменять, модифицировать
поведение унаследованных родительских методов, приспосабливать их к
особенностям объектов конкретных классов.
В целом это позволяет создавать более гибкие объектные
программы, которые могут изменять свое поведение в процессе своего
выполнения в зависимости от текущей ситуации.
Здесь ОЧЕНЬ важно подчеркнуть следующее: в объектной программе,
созданной с помощью механизма переопределения, существует несколько
вариантов реализации одного и того же виртуального метода
(многоформность, однако!), и решение о том какой из них вызывать
принимается во время выполнения программы в зависимости от того,
объекту какого класса требуется этот метод. Отсюда следует, что часть
работы по связыванию программного кода переносится с этапа
компиляции/компоновки на этап выполнения. Поскольку связывание
программного кода во время выполнения программы требует определенных
затрат, то при проектировании класса возникает вопрос о соотношении
переопределяемых и не переопределяемых (т.е. обычных) методов. Ответ на
этот вопрос сильно зависит от специфики решаемой задачи и часто является
весьма непростым.
Разобьем этот вопрос на два подвопроса:
будут ли в дочернем классе переопределяться унаследованные методы
(если это вообще разрешено родителем) и какие именно
какие новые методы, впервые вводимые в разрабатываемом классе,
имеет смысл разрешить к переопределению в последующих
производных классах
В первом случае в проектируемый класс полностью копируется
сигнатура родительского метода (или нескольких методов), в заголовке
метода он описывается как переопределяемый (виртуальный) с помощью
специальной директивы и дается его программная реализация (разумеется,
отличающаяся от родительской).
Во втором случае метод(ы) также в заголовке помечается специальным
образом как виртуальный и либо приводится его базовая программная
реализация, либо (достаточно часто) он объявляется абстрактным.
В итоге, в самом общем случае проектируемый класс может содержать
следующие разновидности методов:
обычные унаследованные методы, которые присутствуют в классе
неявно
переопределенные унаследованные методы (виртуальные)
вновь введенные в классе обычные невиртуальные методы
вновь введенные в классе методы, разрешенные к переопределению,
т.е. объявленные виртуальными
Вся эта информация о методах необходима для правильной обработки
описания класса компилятором/компоновщиком. При этом обычные и
виртуальные методы обрабатываются по-разному.
Для обычных методов используется схема раннего связывания (early
binding, статическая компоновка), когда замена (связывание) имени
подпрограммы необходимым набором команд выполняется при разработке
программы. В результате создается полностью готовый к выполнению
программный код, в котором все адресные связи настроены необходимым
образом, и поэтому выполнение такого кода происходит максимально
быстро. Но с другой стороны, такая жесткая фиксация адресных связей не
позволяет менять траекторию выполнения программы.
Для виртуальных методов используется другая схема – так называемое
позднее
связывание
или
динамическая
компоновка.
Здесь
компилятор/компоновщик выполняет лишь некоторую подготовительную
работу, а окончательное связывание имени подпрограммы с исполняемым
кодом происходит при работе программы. В этом случае создаваемый
программный код имеет «полуфабрикатный» вид, в нем отсутствуют
некоторые необходимые программные фрагменты, что позволяет при
выполнении программы в одной и той же ее точке динамически
подключать разные фрагменты программного кода. Это безусловно
повышает гибкость объектной программы, но несколько замедляет ее
выполнение.
Эти факторы обязательно надо учитывать при проектировании классов
и решении вопроса о степени использования виртуальных методов. Если в
классе все методы будут объявлены как обычные, то степень гибкости
программы будет минимальной, но зато скорость выполнения –
максимально возможной (при прочих равных условиях). Наоборот, если все
методы будут виртуальными, то гибкость программы будет максимальной,
но время выполнения – больше. На практике обычно стремятся найти
разумный компромисс между гибкостью и скоростью выполнения.
Действительно, далеко не все методы следует объявлять виртуальными.
Например, вряд ли имеет смысл объявлять виртуальными методы доступа к
закрытым свойствам класса. Также виртуальными не объявляют
конструкторы в силу их особого предназначения. А вот метод прорисовки
графических объектов разумно объявить виртуальным: все такие методы
имеют одинаковое имя (поскольку параметров нет, то сигнатура сводится
просто к имени), но свою программную реализацию в каждом классе. Это
открывает целый ряд интересных возможностей, которые описываются в
следующих разделах пособия.
Теперь немного поговорим о тех внутренних механизмах, на которых
основана динамическая компоновка. Если в классе объявлены виртуальные
методы, то компилятор/компоновщик в точках вызова этих методов
формирует специальные конструкции, часто называемые «заглушками».
Кроме того, создаются специальные структуры данных с информацией об
используемых в классе виртуальных методах. Часто такие структуры
называют Таблицами Виртуальных Методов (VMT, Virtual Method
Table). В разных языках эти таблицы реализованы немного по-разному, но
общий принцип их использования одинаков: предоставить механизму
динамической компоновки информацию о виртуальных методах класса.
Для каждого класса, где есть хотя бы один виртуальный метод
создается своя отдельная таблица. Каждая такая таблица во время
выполнения программы находится в оперативной памяти и содержит адреса
размещения в памяти кода соответствующих виртуальных методов. Для
доступа к этим таблицам у каждого объекта есть внутреннее скрытое от
разработчика поле, которое используется для хранения адреса
соответствующей таблицы. Другими словами, каждый объект связан со
своей таблицей виртуальных методов. Заполнение этого поля поручено