Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

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



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

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

Переопределение необходимо отличать от перегрузки (overloading) методов. Перегрузка возникает, когда в одном классе имеются несколько одноименных методов, но с разными сигнатурами. Переопределение возникает, когда в разных классах встречаются одноименные методы с полностью совпадающими сигнатурами.

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

· общие методы, определяющие общее поведение либо всех, либо большинства объектов в этой иерархии; целесообразно эти методы реализовывать как можно выше в иерархии, чтобы дать возможность как можно большему числу потомков наследовать эти методы;

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

Например, для библиотеки графических фигур уникальным методом можно считать прорисовку каждого объекта-примитива. Общим методом можно считать метод перемещения объекта в новую точку. Этот метод реализуется для любых примитивов по одному и тому же алгоритму: стереть старое изображение -> изменить базовую точку -> нарисовать на новом месте. Поэтому метод перемещения целесообразно реализовывать на самом верхнем уровне, а именно - в классе фигур. Но здесь возникает одна проблема: часто программная реализация общего метода внутри себя содержит вызовы уникальных методов. Вместо того, чтобы реализовывать общий метод в каждом классе, можно использовать механизм динамической настройки общего метода на конкретные уникальные методы в зависимости от того, какой объект обращается к общему методу. Общий метод, реализованный на верхнем уровне иерархии, можно рассматривать лишь как заготовку полноценного работоспособного кода. В местах вызовов уникальных методов в такой код вставляются лишь специальные «заглушки». Полноценным общий метод становится, когда при выполнении программы к нему вместо «заглушек» подключается программный код необходимых уникальных методов.

Реализация динамической настройки общих методов требует определенной работы как от компилятора/компоновщика, так и от среды, поддерживающей работу приложения. Обычно компилятор/компоновщик при создании исполняемого кода использует механизм статической компоновки (другое название - раннее связывание). В этом случае компилятор обрабатывает все вызовы подпрограмм и включает эти подпрограммы в единый исполняемый код. В этом коде настроены все связи между подпрограммами. Раннее связывание позволяет создавать полностью готовый к выполнению программный код. Наоборот, при позднем связывании исполняемый код может не содержать некоторые необходимые подпрограммы. Окончательная настройка производится при выполнении приложения: если исполняемый код обращается к отсутствующей подпрограмме, то с помощью операционной системы выполняется поиск запрошенной подпрограммы в памяти или на жестком диске, при необходимости загрузка ее в память и после этого передача управления вызванному коду.

Статическая компоновка создает максимально быстрый код, но в него очень сложно вносить какие-либо изменения. Наоборот, динамическая компоновка позволяет создавать гибкие, динамически настраиваемые программы, но выполняющиеся немного медленнее. Именно динамическая компоновка лежит в основе использования переопределяемых полиморфных методов.

При разработке нового класса все его методы должны быть разбиты на две группы – статически компонуемые (назовем их СК-методами) и динамически компонуемые (ДК-методы). Для СК-методов выполняется обычная статическая компоновка. Для ДК-методов компилятор/компоновщик должен в исполняемый код добавить необходимую служебную информацию. Как минимум, эта информация должна содержать список всех ДК-методов класса. При запуске приложения на основе этой информации в памяти строятся специальные структуры данных – таблицы виртуальных методов (ТВМ, или Virtual Method Table, VMT). Такая таблица строится отдельно для каждого класса, где имеется хотя бы один виртуальный метод. При выполнении приложения такая таблица содержит адреса размещения в памяти всех динамически подключаемых виртуальных методов. Все объекты некоторого класса имеют внутреннее скрытое поле, в котором хранится адрес соответствующей ТВМ, тем самым каждый объект знает размещение в памяти всех своих виртуальных методов. Именно по этой схеме выполняется динамическое подключение уникальных виртуальных методов к общим методам.

В качестве примера рассмотрим описанную ранее иерархию графических фигур и внесем в нее следующие изменения. В базовом классе фигур TFigure объявим абстрактный метод прорисовки Show переопределяемым, т.е. виртуальным. Это даст возможность в дочерних классах вводить свои реализации метода с общим именем Show. Кроме того, метод перемещения MoveTo в классе фигур сделаем не абстрактным, а реальным, реализующим шаги, необходимые для перемещения любой фигуры (стираем вызовом Show, изменяем базовую точку, рисуем вызовом Show).

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

Класс TFigure
x, y: integer
Create; Show; virtual; MoveTo; GetX/GetY; SetXY;
Фрагмент UML-диаграммы классов для иерархии графических фигур теперь можно представить следующим образом.

 
 


Предположим, что созданы два объекта-окружности C1 и С2 и два объекта-прямоугольника R1 и R2. Пусть в программе реализовано несколько обращений к методу MoveTo со стороны разных объектов:

С1.MoveTo(…); R1.MoveTo (…); C2.MoveTo (…); R2.MoveTo (…);

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

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

 
 


Связь объекта с соответствующей ТВМ устанавливается конструктором при создании объекта.

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

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

TFigure = class

procedure Show; virtual;

В дочерних подклассах все переопределяемые методы снабжаются директивой override:

TCircle = class (TFigure)

procedure Show; override;

В языке C++ по умолчанию все методы также считаются статически компонуемыми. Для включения динамической компоновки используется директива virtual:

class Figure

{ virtualvoid Show();... }

В дочерних подклассах компилятор автоматически считает переопределяемыми все методы, сигнатуры которых полностью совпадают с заголовком виртуального метода, и поэтому никаких дополнительных директив не требуется, хотя для ясности рекомендуется снабжать заголовки переопределяемых методов директивой virtual:

class Circle: Figure

{ virtualvoid Show();

// а можно просто void Show();... }

В языке Java (внимание!) по умолчанию все методы считаются динамически компонуемыми и, следовательно, переопределяемыми. Для того чтобы запретить динамическую компоновку, т.е. объявить некоторые методы статически компонуемыми, в заголовке метода используется директива final:

class Figure

{ publicvoid Show(); // для динамической компоновки

publicfinalvoid MoveTo (…); // для статической компоновки

};

class Circle extends Figure

{ publicvoid Show();...

};





Дата публикования: 2014-11-02; Прочитано: 1064 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.011 с)...