Полиморфизм - один из основных приемов создания гибкой архитектуры приложений. Он обеспечивает повторное использование и расширяемость существующей кодовой базы. В C++ полиморфизм реализуется несколькими способами.
Здесь мы рассмотрим пример использования полиморфизма в C++, основанный на объектно-ориентированном программировании и применении виртуальных функций. Виртуальными называют такие функции-члены, которые могут быть переопределены в классах-наследниках.
Допустим, мы разрабатываем мультимедийную библиотеку для работы с веб-камерами. Для этого мы создаем класс WebCamera
:
// Класс для получения кадров с веб-камеры
class WebCamera {
public:
// Выбор камеры осуществляется по ее идентификатору
explicit WebCamera( int cameraID );
~WebCamera();
// Запускает обработку потока кадров в режиме реального времени
// После запуска веб-камера начинает "видеть"
void startCapture();
// Возвращает текущий кадр, если веб-камера активна
Frame takePicture() const;
// Останавливает обработку потока кадров. Веб-камера "засыпает"
void stopCapture();
};
Воспользоваться этим классом можно следующим образом:
WebCamera webCamera( 0 );
try {
webCamera.startCapture();
Frame frame = webCamera.takePicture();
webCamera.stopCapture();
} catch( … ) {
// В реальном приложении нужно обрабатывать конкретные типы исключений
}
Относительно этого класса можно построить целое приложение. Одним из элементов этого приложения, вероятно, станет класс VideoPlayer
для создания трансляции видео-потока на основе данных, получаемых от веб-камеры:
// Класс для трансляции видео-потока на основе кадров, получаемых от веб-камеры
class VideoPlayer {
public:
// Привязка осуществляется к конкретной веб-камере
explicit VideoPlayer( WebCamera* webCamera );
~VideoPlayer();
// Используем паттерн Наблюдатель для распространения данных видео-потока
void registerObserver( VideoPlayerObserver* observer );
void unregisterObserver( VideoPlayerObserver* observer );
// Функции управления трансляцией видео-потока
void play();
void pause();
void resume();
void stop();
};
Обратите внимание, что для трансляции видео-потока используется паттерн Наблюдатель.
Все выглядит неплохо. Но предположим, что в какой-то момент появилась необходимость обеспечить поддержку еще одного типа камер, которые используют специфический драйвер и не поддерживаются текущей реализацией WebCamera
. Можно изменить реализацию класса WebCamera
, вставив условные конструкции с ветвлением по типу камеры. Но такая архитектура окажется довольно громоздкой и неудобной для сопровождения. Гораздо лучше воспользоваться полиморфизмом.
Сразу возникает желание создать абстрактный класс на подобии AbstractCamera
. Но не стоит спешить. Устройств ввода потоков кадров очень много. И не все они являются камерами. Например, может потребоваться добавить поддержку электронного микроскопа. Или возникнет необходимость получать кадры с сетевой веб-камеры из интернета. Чтобы воспользоваться преимуществами повторного использования, лучше заранее предусмотреть и эти случаи, выбрав в качестве имени для базового класса AbstractVideoStream
:
// Абстрактный класс видео-потока
class AbstractVideoStream {
public:
virtual ~AbstractVideoStream();
// Управление видео-потоком
virtual void start() = 0;
virtual void stop() = 0;
// Возвращает текущий кадр, если видео-поток активен
virtual Frame getFrame() const = 0;
};
Концепция класса AbstractVideoStream
немного отличается от WebCamera
, но аналогия легко прослеживается. Не забывайте, что класс, предназначенный для наследования, должен иметь виртуальный деструктор, иначе могут возникнуть утечки памяти. Также не забывайте про ключевое слово virtual
перед объявлением функций-членов. Поскольку осмысленную реализацию для абстрактного класса видео-потока предусмотреть довольно сложно, объявим функции-члены чисто виртуальными, добавив в конце объявления = 0
.
Сделаем WebCamera
наследником AbstractVideoStream
:
// Класс видео-потока веб-камеры
class WebCamera : public AbstractVideoStream {
public:
explicit WebCamera( int cameraID );
~WebCamera();
// startCapture() и stopCature() пришлось заменить:
void start();
void stop();
Frame getFrame() const;
};
В пользовательском коде предпочтительно везде использовать указатели на AbstractVideoStream
, а не на конкретные подклассы:
if( AbstractVideoStream* stream = new WebCamera( 0 ) ) {
try {
stream->start();
Frame frame = stream->takePicture();
stream->stop();
} catch( … ) {
// В реальном приложении нужно обрабатывать конкретные типы исключений
}
delete stream;
}
В VideoPlayer
тоже должен использоваться указатель на AbstractVideoStream
:
// Класс для трансляции видео-потока
class VideoPlayer {
public:
// Привязка осуществляется к конкретному видео-потоку
explicit VideoPlayer( AbstractVideoStream* stream );
// Остальное без изменений…
};
Теперь класс VideoPlayer
может работать с любыми источниками видео-потоков. За счет полиморфизма приложение стало значительно проще расширять. Любой может сам добавить поддержку своего типа видео-потока даже не имея исходных кодов библиотеки.
Представленный пример использует вариацию паттерна Стратегия. Более конкретный пример этого паттерна мы рассматривали, когда говорили о потоках в Qt.
"Виртуальными называют такие функции-члены, которые могут быть переопределены в классах-наследниках" - это неправда. Переопределить можно можно и не виртуальные.
Anonymous:
"Виртуальными называют такие функции-члены, которые могут быть переопределены в классах-наследниках" - это неправда. Переопределить можно можно и не виртуальные.
Это называется перегрузка, я не переопределение.
Anonymous
пример фантазии, а полиморфизма - плохой