В общем случае существуют две модели обработки ошибок: анализ кодов возврата и использование исключений. Несмотря на то, что использование исключений в последнее время подвергается серьезной критике, вплоть до того, что в новых языках программирования от них избавляются, в C++ указанный механизм остается востребованным, и мы также им воспользуемся. Объявления для формирования исключений представлены в Листинг 87.
Листинг 87. Исключения для обработки ошибок (Errors.h)
namespace sensor
{
enum class SensorError: uint32_t // (1)
{
NoError = 0,
NotInitialized = 1,
UnknownSensorType = 2,
UnknownSensorNumber = 3,
SensorIsNotOperable = 4,
DriverIsNotSet = 5,
InvalidArgument = 6,
NotSupportedOperation = 7,
InitDriverError = 8
};
class sensor_exception : public std::exception // (2)
{
public:
sensor_exception(SensorError error);
SensorError code() const;
virtual const char* what() const;
static void throw_exception(SensorError error); // (3)
private:
SensorError code_;
};
}; //namespace sensor
В строке 1 объявлены коды возможных ошибок, в строке 2 объявлен класс исключений. Если при выполнении где-то в коде возникает ошибка, то в этом месте нужно вызвать метод, объявленный в строке 3. Указанный метод выбросит исключение с соответствующим кодом.
Драйвер предназначен для взаимодействия с аппаратным обеспечением. Класс, представляющий обобщенный интерфейс для работы с драйвером, приведен в Листинг 88.
Листинг 88. Интерфейс для работы с драйвером (DriverInterface.h)
namespace sensor
{
class IDriver
{
public:
virtual void initialize() = 0; // (1)
virtual void activate(SensorNumber number) = 0; // (2)
virtual bool isOperable(SensorNumber number) = 0; // (3)
virtual SensorValue readSpot(SensorNumber number) = 0; // (4)
virtual SensorValue readSmooth(SensorNumber number) = 0; // (5)
virtual SensorValue readDerivative(SensorNumber number) = 0; // (6)
virtual ~IDriver() = default;
static DriverPointer createDriver(DriverType type); // (7)
};
}; //namespace sensor
В строке 1 объявлен метод для инициализации драйвера. В строке 2 объявлен метод для активации датчика. В строке 3 объявлен метод, возвращающий признак работоспособности датчика. В строках 4, 5 и 6 объявлены методы для чтения соответственно текущих, сглаженных и производных значений. Метод в строке 7 представляет собой фабрику классов, в котором происходит создание класса соответствующего типа.
От общего интерфейса наследуются классы, реализующие драйверы различных типов. В нашей системе реализованы три типа драйверов: драйвер для работы с шиной USB; драйвер для работы через сеть Ethernet; имитируемый драйвер. Диаграмма классов изображена на Рис. 28.
Рис. 28. Диаграмма классов, реализующих драйверы
Драйверы для работы с физическими устройствами формируют команды, посылают их через соответствующие протоколы и возвращают результаты. Реализацию этих драйверов мы рассматривать не будем, поскольку работа с hardware – это отдельная тема, для изучения которой требуется не одна книга. Для нас представляет интерес реализация имитируемого драйвера.
Очевидно, что имитируемый драйвер должен возвращать заранее заданные значения. Самое простое решение, лежащее на поверхности, заключается в том, чтобы хранить эти значения в глобальных или статических переменных и возвращать их в соответствующих методах. Однако в этом случае имитация будет очень примитивной: для всех датчиков будет возвращаться одно и то же значение. Можно хранить свое возвращаемое значение в каждом классе датчика, что больше похоже на работу в реальной системе, но это также не лишено недостатков: мы не можем моделировать изменения показателей в динамике. Лучшим решением было бы предоставить возможность пользователю возвратить значение в момент запроса, для чего нам, конечно же, понадобится обратный вызов. Обратный вызов будет использоваться по схеме «Запрос данных» (см. п. 1.2.1).
Итак, для реализации интерфейса имитируемого драйвера нам понадобятся дополнительные методы и определения (см. Листинг 89).
Листинг 89. Имитируемый драйвер (DriverImpl.h)
class DriverSimulation : public IDriver
{
public:
enum ReadType { READ_SPOT = 0, READ_SMOOTH = 1, READ_DERIVATIVE = 2 }; // (1)
using OnReadValue = std::function; // (2)
using OnOperable = std::function; // (3)
void initialize() override;
void activate(SensorNumber number) override;
Читать дальше