При разработке любой программы сложнее чем простейшая утилита командной строки встает вопрос об организации подсистем и их взаимодействии. Он включает в себя декомпозицию функционала программы на подсистемы и организацию способа их взаимодействия. Именно об организации взаимодействия подсистем, а так же управления их созданием и удалением будет идти речь в статье.
Пример
Необходимо написать программу, которая захватывает изображение с веб-камеры обрабатывает его и отображает результат на экран в реальном времени. После декомпозиции выявлено 3 сущности:
- Подсистема захвата видеопотока.
- Подсистема отображения.
- Подсистемы обработки видеопотока.
class processor
{
public:
static processor & singleton();
void * process(void *data);
private:
processor() {};
processor(const processor&) {};
};
class capturer
{
public:
capturer(const std::string &file_name);
void * frame();
};
class render
{
public:
render(const std::string &name, int width, int height);
void start();
void stop();
void render_frame(void *frame);
};
Необходимо организовать взаимодействие, создание и удаление этих подсистем. При этом можно выделить следующие требования к способу организации:
- Читаемость кода (до 90% времени программисты тратят на чтение чужого кода).
- Скорость работы программы (способ организации не должен накладывать дополнительные расходы).
- Гибкость (возможность добавлять различные подсистемы, независимо от их интерфейса).
- Простое и явное управление временем жизни объектов подсистем.
Способы решения
За время своей работы профессиональным программистом я успел поработать в нескольких компаниях, известных и неизвестных. Далее я приведу способы решения описываемой в данной статье проблемы, которые встречал на практике.
Синглтоны
Работа со всеми подсистемами в программе осуществляется при помощи синглтонов. Достаточно хорошее решение, но имеет один большой недостаток – это управление временем жизни объектов. И если с созданием синглтонов еще можно сделать воркэраунд, вызывая их явно в нужно порядке, то с удалением всегда появляются проблемы. Нередко проблема удаления объектов-синглтонов решается при помощи черной магии вроде синглтона типа феникс.
Божественный объект
Все подсистемы включаются как поля одного класса, который при создании их всех инициализирует в нужном порядке, а потом и разрушает. Как правило у такого божественного объекта в конструкторе предаются параметры для инициализации всех подсистем, он раздувается до космических размеров. Так же возникает проблема организации доступа одной подсистемы к другой, т.к. при этом мы должны явно передавать в каждую подсистему указатель на божественный объект.
Сигналы
Каждая подсистема реализует некий механизм обработки событий/сигналов из общей для всего приложения очереди. Идеологически очень хорошее решение, но на практике приводит к необходимости писать много ненужного кода, реализации ненужных интерфейсов и т.д. К тому же немного страдает скорость выполнения программы.
Мой способ организация подсистем
Этот способ является результатом последовательного развития моих мыслей и поиска оптимального решения на протяжении последних лет. Итак задача ясна, представляю сразу готовое решение с комментариями.
Выделяется 3 типа подсистем:
- Управляемые(managed). Созданием и удалением этих подсистем мы занимаемся сами. К тому же они обязаны реализовывать некоторый заданный интерфейс, например, методы start() и stop(). Так же эти подсистемы имеют доступ к другим подсистемам в программе.
- Неуправляемые(unmanaged). Создаем и удаляем их мы сами, но не накладываем никаких ограничений на интерфейс.
- Внешние(external). Не управляем созданием и удалением. Нет ограничений на интерфейс.
Обращение ко всем подсистемам универсально и осуществляется через менеджер подсистем(master).
Код
Интерфейс управлемых подсистем
// subsystem.hpp
class master_t;
class subsystem_t
{
friend class master_t;
public:
virtual void start() {}
virtual void stop() {}
virtual inline ~subsystem_t() = 0;
inline master_t & master();
private:
master_t *m_master;
};
// Implementation
inline subsystem_t::~subsystem_t()
{
}
inline master_t & subsystem_t::master()
{
return *m_master;
}
Менеджер подсистем
// master.hpp
# include "subsystem.hpp"
# include "noncopyable.hpp"
# include <vector>
# include <cassert>
class master_t : private noncopyable_t
{
public:
template <typename SubsystemType, typename... Args>
inline void add_managed_subsystem(Args ...args);
template <typename SubsystemType, typename... Args>
inline void add_unmanaged_subsystem(Args ...args);
template <typename SubsystemType>
inline void add_external_subsystem(SubsystemType *raw_pointer);
template <typename SubsystemType>
inline SubsystemType & subsystem();
inline void start();
inline void stop();
inline ~master_t();
private:
std::vector<subsystem_t *> m_subsystems;
};
// Implementation
inline void master_t::start()
{
for (auto &subsystem : m_subsystems)
{
subsystem->start();
}
}
inline void master_t::stop()
{
for (auto &subsystem : m_subsystems)
{
subsystem->stop();
}
}
inline master_t::~master_t()
{
for (auto &subsystem : m_subsystems)
{
delete subsystem;
}
}
namespace internal
{
template <typename SubsystemType>
inline SubsystemType ** subsystem_instance()
{
static SubsystemType *instance = 0;
return &instance;
}
template <typename SubsystemType>
struct unmanaged_holder_t : public subsystem_t
{
template <typename... Args>
inline unmanaged_holder_t(Args ...args) : holder(args...) {}
SubsystemType holder;
};
}
template <typename SubsystemType, typename... Args>
inline void master_t::add_managed_subsystem(Args ...args)
{
SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
assert(*instance == 0);
*instance = new SubsystemType(args...);
static_cast<subsystem_t *>(*instance)->m_master = this;
m_subsystems.push_back(*instance);
}
template <typename SubsystemType, typename... Args>
inline void master_t::add_unmanaged_subsystem(Args ...args)
{
SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
assert(*instance == 0);
internal::unmanaged_holder_t<SubsystemType> *unmanaged_holder = new internal::unmanaged_holder_t<SubsystemType>(args...);
*instance = &(unmanaged_holder->holder);
m_subsystems.push_back(unmanaged_holder);
}
template <typename SubsystemType>
inline void master_t::add_external_subsystem(SubsystemType *raw_pointer)
{
SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
assert(*instance == 0);
*instance = raw_pointer;
}
template <typename SubsystemType>
inline SubsystemType & master_t::subsystem()
{
SubsystemType **instance = internal::subsystem_instance<SubsystemType>();
assert(*instance != 0);
return **instance;
}
Пример использования
// main.cpp
#include "subsystem.hpp"
#include "master.hpp"
#include <iostream>
#include <string>
// external
class processor
{
public:
static processor & singleton()
{
static processor instance;
return instance;
}
void * process(void *data)
{
std::cout << "processor: process" << std::endl;
return data;
}
private:
processor() {};
processor(const processor&) {};
};
// unmanaged
class capturer
{
public:
capturer(const std::string &file_name)
: m_file_name(file_name)
{}
void * frame()
{
return nullptr;
}
private:
const std::string m_file_name;
};
// managed
class render : public subsystem_t
{
public:
render(const std::string &name, int width, int height)
: m_name(name)
, m_width(width)
, m_height(height)
{
}
virtual void start()
{
std::cout << "render start" << std::endl;
}
virtual void stop()
{
std::cout << "render stop" << std::endl;
}
void render_frame(void *frame)
{
void *processed = master().subsystem<processor>().process(frame);
std::cout << "Render[" << m_name << "]: render_frame " << processed << std::endl;
}
private:
const std::string m_name;
int m_width;
int m_height;
};
int main()
{
master_t master;
master.add_unmanaged_subsystem<capturer>("test1.avi");
master.add_managed_subsystem<render>("wnd1:", 640, 480);
master.add_external_subsystem<processor>(&processor::singleton());
master.start();
//while (true)
void *frame = master.subsystem<capturer>().frame();
master.subsystem<render>().render_frame(frame);
// }
master.stop();
return 0;
}
Автор: Inkooboo