Обратные вызовы в C++ - [3]
Рис. 4. Результат вычисления с помощью обратного вызова
1.2.3. Перебор элементов
Представим, что мы разрабатываем модуль сетевого обмена. Как пользователю узнать, какие протоколы поддерживаются?
Самое простое решение – получить количество поддерживаемых протоколов, а затем запрашивать их имена по порядковому номеру. Данный способ легко реализуем, если внутри модуля имена протоколов хранятся в массиве. А если имена нужно хранить в списке? Тогда задача усложняется: нужно сделать перебор элементов списка, чтобы получить нужное значение по порядковому номеру. А если имена должны храниться в виде двоичного дерева?
Возможное решение: разработать итератор – специальный класс, который будет осуществлять навигацию по контейнеру. Такой подход реализован, к примеру, в стандартной библиотеке STL, где для каждого контейнера имеется соответствующий итератор. Недостаток этого решения проявляется в том, что мы ограничиваем сферу применения модуля, построенного таким образом: его использовать могут только те компоненты, которые способны интерпретировать вызовы методов C++. Кроме того, итератор привязан к типу используемого контейнера, и при его изменении приходится перекомпилировать все связанные компоненты.
А что, если реализовать итератор с помощью набора функций, без использования классов? Интерфейс получается довольно сложным: необходимы отдельные функции для создания итератора, запроса значений, уничтожения итератора; необходимо объявить тип данных для хранения итератора; необходимо предусмотреть уничтожение итератора в случае возникновения исключений.
Простое и эффективное решение указанных проблем представлено на Рис. 5. Код, обрабатывающий имена поддерживаемых протоколов (например, отображение в пользовательском интерфейсе), упаковывается в отдельный компонент. Для получения протоколов вызывается функция, в которую указанный компонент передается как аргумент. Функция перебирает хранимые значения, для каждого значения через сохраненный аргумент вызывается код обработки, имя протокола передается как параметр.
Рис. 5. Просмотр элементов с помощью обратных вызовов
1.2.4. Уведомление о событиях
Представим, что мы в системе запустили таймер, и нам нужно получить уведомление о срабатывании таймера. Самое простое решение – в процессе выполнения опрашивать таймер и анализировать, не истекло ли время. Как часто нужно делать опрос? Слишком часто – теряется производительность, слишком редко – теряется точность. Кроме того, приходится постоянно в определенных участках кода вставлять вызов опроса. Учитывая, что в программе могут работать несколько потоков, опрашивать таймер они будут с разной частотой, и каждый поток обнаружит срабатывание таймера в разное время.
Простое и эффективное решение указанных проблем представлено на Рис. 6. Код, обрабатывающий срабатывание таймера, упаковывается в отдельный компонент. Когда запускается таймер, этот компонент как аргумент передается таймеру, и когда таймер сработает, через сохраненный аргумент будет вызван код обработки. По такому же принципу можно организовать асинхронный ввод-вывод, обработку прерываний и т. п.
Рис. 6. Уведомление о срабатывании таймера с помощью обратного вызова
Итак, мы рассмотрели типовые задачи, в которых используются обратные вызовы. Как видим, подставляя соответствующие аргументы, можно запускать на выполнение различные участки программного кода. Отсюда можно сделать вывод, что обратные вызовы целесообразно использовать в случаях, когда требуется динамическая модификация поведения программы во время выполнения.
1.3. Модель обратных вызовов
1.3.1. Определения и термины
Модель обратных вызовов изображена на Рис. 7. Структурно она состоит из двух частей: исполнитель и инициатор.
Исполнитель – это компонент, в который упаковывается код обратного вызова (исполняемый код). Исполнитель также содержит контекст, который представляет собой совокупность данных, влияющих на поведение исполняемого кода.
Инициатор – это компонент, который осуществляет обратный вызов. Перед началом работы выполняется настройка, при которой исполнитель как аргумент вместе с контекстом сохраняются в инициаторе. Затем инициатор запускается, и в нужный момент, используя хранимый аргумент, он делает вызов исполняемого кода. В качестве входных параметров в этот код передается сохраненный контекст и информация вызова, которая представляет собой значения, формируемые инициатором.
Рис. 7. Модель обратных вызовов
Дадим формальные определения используемых терминов.
Исполнитель: компонент, который реализует исполняемый код обратного вызова.
Инициатор: компонент, который осуществляет обратный вызов.
Аргумент: хранимая точка входа в код обратного вызова.
Настройка: процедура сохранения аргумента.
Информация вызова: значения, которые формируются инициатором и передаются в исполнитель.
Контекст: множество переменных и состояний, которые влияют на поведение исполняемого кода.
В процессе реализации обратного вызова нам нужно ответить на следующие вопросы.
1. Как оформить исполняемый код, чтобы он мог быть вызван инициатором?
2. Как хранить аргумент?
Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.
Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.
В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.
В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.
В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.
Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.