В строке 5 приведена реализация интерфейсной функции для настройки драйвера. В строке 6 конвертируется переданное числовое значение в перечисление C++ (будет рассмотрено ниже). В строке 7 объявлен оператор switch, в котором анализируется полученное значение перечисления, и вызывается вспомогательная функция с соответствующими параметрами.
В функции API для задания типа драйвера используются числовые значения, а в интерфейсном классе используются перечисления C++. Для того, чтобы сконвертировать числовое значение в перечисление, используется вспомогательный класс EnumConverter (Листинг 109)
template <typename Enum> // (1)
class EnumConverter
{
public:
template<typename ConvValueType> // (2)
void convert(ConvValueType value, std::initializer_list<Enum> list) // (3)
{
isError_ = true;
for (Enum item : list) // (4)
{
if (static_cast<ConvValueType>(item) == value) // (5)
{
result_ = item; // (6)
isError_ = false;
break;
}
}
};
bool error() const { return isError_; }
Enum result() const { return result_; }
private:
bool isError_;
Enum result_;
};
В строке 1 объявлен шаблонный класс, параметром которого является тип перечисления. Конвертация происходит в функции 2, которая объявлена в виде шаблона, параметром шаблона является тип числового значения для конвертации. Функция принимает число, которое должно быть сконвертировано, а также список значений перечисления (строка 3). Реализация пробегает по всем элементам списка (строка 4) и, если какой-то из элементов списка перечисления равен переданному значению, запоминает это значение перечисления в качестве результата (строки 5,6).
6.3.6. Обратные вызовы
Касательно обратных вызовов мы имеем следующую ситуацию. В системном API контекст вызова передается с помощью указателей на данные, по-другому организация передачи контекста здесь невозможна (см. п. 2.1.2). В интерфейсном классе указатель на данные не используется, поскольку в C++ имеется множество гораздо более изящных способов передачи контекста. Вот тут-то нам и понадобится перенаправление вызовов (см. п. 4.6.2). Реализация одной из интерфейсных функций API, использующей перенаправление вызовов, приведена в Листинг 110.
ErrorCode readSensorValues(SensorValueCallback callback, void* pContextData)
{
ErrorCode error = ERROR_NO;
try
{
using namespace std::placeholders;
g_SensorControl->readSensorValues(std::bind(callback,_1,_2,pContextData)); // (1)
}
catch (sensor::sensor_exception& e)
{
error = e.code();
}
return error;
}
В общем-то, вся реализация заключается в вызове метода интерфейсного класса (строка 1), в который вместо непосредственно обратного вызова передается объект связывания. Функция обратного вызова, объявленная в интерфейсе API, принимает 3 входных параметра: номер датчика, значение датчика и указатель на контекст. Когда будет происходить обратный вызов, то объект связывания вызовет назначенную функцию, в которую передаст первые два параметра исходной функции, а в третий параметр будет передан переданный указатель на контекст.
6.4. Итоги
На примере разработки модуля управления датчиками кратко описаны типовые этапы проектирования: описание технического задания; оформление сценариев функционирования системы; декомпозиция и формирование архитектуры. Затем рассмотрена реализация классов с акцентом на использовании обратных вызовов. И в заключение показан процесс создания системного API и трудности, с которыми сталкивается разработчик при реализации концепции «API как оболочка». Как можно увидеть в рассмотренном примере, в практике разработки ПО существует множество ситуаций, когда целесообразно использовать обратные вызовы как элементы дизайна компонентов системы.
Заключение
Итак, наше повествование подходит к концу, пора подвести некоторые итоги.
Обратный вызов – это паттерн, в котором какой-либо исполняемый код как аргумент передается в другой код. Ожидается, что через сохраненный аргумент исполняемый код будет запущен в какой-то момент времени. Типовые задачи, решаемые с помощью обратных вызовов, следующие: запрос данных; вычисления по запросу; перебор элементов; уведомления о событиях.