IT Notes

Пример полиморфизма в C++ на основе ООП

Полиморфизм - один из основных приемов создания гибкой архитектуры приложений. Он обеспечивает повторное использование и расширяемость существующей кодовой базы. В 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:

"Виртуальными называют такие функции-члены, которые могут быть переопределены в классах-наследниках" - это неправда. Переопределить можно можно и не виртуальные.

Это называется перегрузка, я не переопределение.