Выбрать главу

Если типы и количество получателей известны на этапе компиляции и не планируется их изменение в процессе выполнения программы, то мы имеем статический набор получателей. Распределитель для статического набора можно реализовать в виде функции, в этом случае распределитель структурно совпадает с распределяющей функцией.

В общем случае распределяющая функция принимает набор объектов и набор данных вызова. Эти наборы могут упаковываться в кортеж и пакет параметров в различных комбинациях. С точки зрения дизайна каждый способ упаковки имеет свои преимущества и недостатки, с точки зрения эффективности они равноценны.

Если требуются результаты выполнения вызовов, то они реализуются с помощью отдельной распределяющей функции, которая возвращает результаты в виде кортежа.

Зачастую бывает удобно реализовать распределитель для статического набора в виде класса, в котором объекты вызова хранятся в кортеже, а распределяющей функцией выступает перегруженный оператор. Здесь возникает проблема, как использовать класс с возвратом результатов выполнения и без возврата: перегруженный оператор имеет одинаковый набор входных параметров, различается только наличие и отсутствие возвращаемого значения. Выходом будет реализация двух отдельных классов либо общий класс с дополнительным параметром – индикатором. Во втором случае теряется возможность автоматического вывода типа.

Если типы и количество получателей заранее неизвестны и изменяются в процессе выполнения программы, то мы имеем динамический набор получателей. Он реализуется в виде класса с контейнером, в котором хранятся универсальные аргументы.

Если необходима передача вызовов не всем получателям, а только некоторым, то используется адресное распределение. Поскольку тип используемого адреса заранее не определен, то для сравнения адресов нужно использовать предикаты.

На этом изложение теоретического материала можно считать законченным. Далее рассмотрим, как обратные вызовы используются в практике разработки ПО.

6. Практическое использование обратных вызовов

Итак, мы изучили теоретические основы проектирования обратных вызовов, теперь пришло время продемонстрировать, как они используются в реальных системах. Для иллюстрации мы воспользуемся примером разработки модуля управления датчиками из проекта «автоматизированная система управления технологическими процессами», в котором когда-то принимал участие автор. Данный пример адаптирован, в нем опущены многие детали, которые не имеют отношения к рассматриваемой теме. Мы пройдемся через основные этапы проектирования и проследим, как обратные вызовы используются в реальных инженерных задачах.

Подробное описание всех компонентов модуля заняло бы слишком много места и навряд ли имеет практическую ценность, поэтому мы будем рассматривать самые общие принципы функционирования с акцентом на использование обратных вызовов. Полностью проект можно посмотреть здесь: https://github.com/Tkachenko-vitaliy/Callbacks/tree/master/Sensor.

6.1. Разработка архитектуры

6.1.1. Техническое задание

Первый вопрос, который должен быть задан перед началом разработки чего бы то ни было, звучит следующим образом: что мы будем разрабатывать и что мы хотим в итоге получить? Этот вопрос совсем не тривиальный, как может показаться вначале. Без ясного осознания конечной цели, без четкого понимания свойств и характеристик, которыми должна обладать проектируемая система, разработка может растянуться до бесконечности: происходят постоянные переделки, доработки, хаотичная реализация все новых и новых функций с не очень понятной ценностью, и т. п. В итоге, вместо результата мы сосредотачиваемся на процессе, а конечная цель пропадает где-то за горизонтом. Не сталкивались с такими проектами? Что ж, вам крупно повезло; чтобы также везло в дальнейшем, и подобные проекты в вашей карьере отсутствовали, любое проектирование нужно начинать с постановки целей, которые выражаются в требованиях, предъявляемых к системе. В нашем случае они будут следующими.

Разработать модуль управления датчиками, который должен обеспечивать:

1. Настройку конфигурации датчиков и возможность ее изменения в процессе работы.

2. Отслеживание состояния и определение неисправности датчиков.

3. Считывание показаний отдельных датчиков.

4. Считывание показаний всех работоспособных датчиков.

5. Асинхронный опрос показаний.

6. Возможность получения минимальных и максимальных значений для группы датчиков.