Событие (объектно-ориентированное программирование)

Событие (англ. event) — понятие в разработке программного обеспечения, применяемое в рамках событийно-ориентированной парадигмы программирования для управления потоком исполнения программы. Программа в таком случае не исполняется линейно, а специальные процедуры обработки событий (англ. listener, англ. observer, англ. event handler) выполняются каждый раз при наступлении определённого события. Событийно-ориентированное программирование относится к методам параллельного программирования, соответственно, обладает их преимуществами и недостатками.

Мотивация

В компьютерных программах часто возникает необходимость ожидания ресурсов, например, при запросах памяти или доступе к устройствам, когда требуется ждать непредсказуемое количество времени, или когда события могут возникать спонтанно — например, щелчки мышью, нажатия клавиш или комбинаций клавиш, а также открытие, закрытие или изменение размера окна, срабатывание таймеров пользователем. В классической программной модели без событий или при единственном, последовательном потоке управления ожидание события, например успешного выделения ресурса, реализуется через активное ожидание, называемое также англ. polling.

Polling обладает рядом недостатков: выполнение программы (по сути) приостанавливается до наступления нужного события, что ухудшает производительность и делает время отклика непредсказуемым. Во время активного ожидания невозможно реагировать на другие события, из-за чего такие события могут быть потеряны. Также активное ожидание может потреблять непредсказуемо много процессорного времени, поскольку проверка наступления события осуществляется повторно неопределённое число раз.

Определение

В программировании обработчиком событий (англ. event handler) называют функцию обратного вызова, которая асинхронно срабатывает при наступлении события. Она определяет действие, которое следует выполнить в ответ на событие. программист реализует функцию или метод, осуществляющий такое действие. В качестве события может выступать элемент пользовательского интерфейса или даже целый HTML-документ. Событие является частью информационной структуры приложения, предоставляемой базовой программной библиотекой[1].

Событийно-ориентированное программирование

Один из подходов к эффективному решению описанных задач — событийно-ориентированное программирование, основанное на англ. inversion of control («инверсии управления»). Это означает, что, в отличие от традиционного основного потока управления, ожидание события делегируется отдельному потоку исполнения (часто реализованному как поток), который активируется при наступлении события и может воздействовать на основной поток программы (см. параллельное программирование).

Технические решения, реализующие эту идею, известны с 1960-х годов: это функция обратного вызова (с привязкой к событию через отдельную подпрограмму) и аппаратные прерывания, которые избавляют от недостатков polling, но, в свою очередь, порождают неизбежные сложности параллельного программирования.

Терминология проектирования событийных систем включает и описания через шаблоны проектирования, в частности, наблюдатель (англ. observer).

Использование

Событийно-ориентированное программирование широко применяется для реализации графических пользовательских интерфейсов, где события, как правило, представляют действия пользователя — нажатие клавиши, щелчок по кнопке и т.д. Другое важное направление — компьютерные симуляции, построенные таким образом, что изменения состояния системы происходят только по событиям и, в свою очередь, сами генерируют события (см. событийно-ориентированная симуляция).

Событийно-ориентированное программирование хорошо сочетается с концепциями объектно-ориентированного программирования (ООП): объекты могут не только определять свойства и методы, но и выступать источниками событий — а также влиять на обработку событий. Обработчики событий (англ. event handler, нем. Ereignishandler) и сами события в таком случае обычно также реализуются как отдельные объекты. Можно рассматривать событийную модель как частный случай слабосвязанного обмена сообщениями между объектами, что, по мнению ряда авторов, всегда было неявной частью ООП[2].

В зависимости от среды программирования событие может иметь только один обработчик (например, в Object Pascal) либо несколько (например, в Visual Basic, C# или при использовании сигналов и слотов).

Программно возможно помечать событие как «обработанное» (англ. consume). Последующие обработчики могут это проверить и воздержаться от дальнейшей обработки.

Примеры

Пример для Microsoft Access

В среде Microsoft Access пользователи могут создавать формы и отчёты с включёнными в них полями. Также существуют области: заголовок формы, заголовок отчёта, групповые заголовки и групповые подытоги, область данных — каждая из которых содержит отдельные поля. Все эти элементы являются объектами.

Обработка объектов разделена на функциональные модули, выполнение которых зависит от наступления определённых событий:

Файл:Ereignis Access.png
Определение событий в MS Access (2003)

В формах события инициируются, в основном, действиями пользователя через интерфейс: событий мыши, ввод с клавиатуры и др., которые обрабатываются механизмом Access Engine. Возможные события для форм:

  • открытие, отображение, перед вводом, изменение, удаление, закрытие
  • для полей формы: когда изменено, при наведении курсора, при клике, при двойном щелчке, при отпускании клавиши
  • для управляющих кнопок: при фокусе, при клике, при двойном щелчке

В отчётах движок инициирует события в зависимости от данных, аналогично принципам нормированного программирования. Возможные события:

  • для всего отчёта: при открытии/закрытии, при начале страницы, при отсутствии данных
  • для областей отчёта (например, групповые заголовки/подытоги): при печати, при форматировании

Кроме стандартной обработки каждого типа событий в Microsoft Access программист может дополнительно задать произвольные действия для любого объекта и события. Например, после изменения поля можно реализовать проверку, при открытии пустого отчёта — вывести сообщение об ошибке, сделать группу невидимой, если она содержит только одну запись, или изменить видимость/содержимое поля в зависимости от условия.

Для таких действий создаётся процедура, в которой с помощью Visual Basic for Applications реализуются необходимые действия (см. рисунок). При наступлении соответствующего события вызывается данная процедура. Если процедура не прописана, событие либо обрабатывается по умолчанию, либо не обрабатывается вовсе (например, при наведении на объект указателя мыши).

Реализация системы событий

Псевдокод

Приведённый ниже псевдокод иллюстрирует простую реализацию системы событий:

Function Event
  listener = []
  call = function()
    for each parallel (l in listener)
      l()

Пример использования:

Клик = new Event
Клик.listener.add(regenGeräusch)
Клик.listener.add(regenBild)
Клик()

JavaScript

Данная простая система событий обеспечивает последовательную обработку и позволяет регистрировать и удалять обработчики событий. Для параллельного выполнения World Wide Web Consortium разрабатывает механизм англ. Web Workers. Описанная реализация может быть использована на языке JavaScript следующим образом:

Formular = function()
{
    this.send = new Event();
}

function sendToServer()
{
    alert("Запрос отправлен на сервер.");
}

function sayThankYou()
{
    alert("Спасибо за заполнение формы.");
}

var umfrage = new Formular();
umfrage.send.addListener(this, "sendToServer");
umfrage.send.addListener(this, "sayThankYou");
umfrage.send();

C++

Следующий пример кода на варианте C++ от Microsoft демонстрирует реализацию методов обработки событий (англ. event handlers) и их связывание с событием. Для подключения и отключения событий используются специальные функции языка C++. Атрибуты события [event_source(native)] и [event_receiver(native)] в стандартном C++ не поддерживаются. Для компиляции под MSVC необходимо отключить режим /permissive-. С другими компиляторами этот пример не работает[3].

#include <iostream>

using namespace std;

// Класс-источник события
[event_source(native)]
class EventSource
{
public:
    // Декларация метода, объявляющего обработчики событий как событие
    __event void MyEvent(int count, int wordCount);
};

// Класс-приёмник события
[event_receiver(native)]
class EventReceiver
{
public:
    // Метод-обработчик события (event handler)
    // Сигнатура совпадает с событием MyEvent
    void MyEventHandler1(int count, int wordCount)
    {
        cout << "MyEventHandler1 asserts that Wikipedia is an important encyclopedia with " << count << " articles and " << wordCount << " words." << endl;
    }

    // Другой обработчик того же события
    void MyEventHandler2(int count, int wordCount)
    {
        cout << "MyEventHandler2 asserts that Uncyclopedia is a funny encyclopedia with " << count << " articles and " << wordCount << " words." << endl;
    }

    // Метод связывает источник события с обработчиками
    void hookEvent(EventSource* pEventSource)
    {
        __hook(&EventSource::MyEvent, pEventSource, &EventReceiver::MyEventHandler1);
        __hook(&EventSource::MyEvent, pEventSource, &EventReceiver::MyEventHandler2);
    }

    // Метод отсоединения обработчиков от события
    void unhookEvent(EventSource* pEventSource)
    {
        __unhook(&EventSource::MyEvent, pEventSource, &EventReceiver::MyEventHandler1);
        __unhook(&EventSource::MyEvent, pEventSource, &EventReceiver::MyEventHandler2);
    }
};

// Главная функция
int main()
{
    EventSource eventSource;
    EventReceiver eventReceiver;

    eventReceiver.hookEvent(&eventSource);
    // Генерируем событие MyEvent — будут вызваны оба обработчика
    __raise eventSource.MyEvent(1000000, 100000000);
    __raise eventSource.MyEvent(100000, 10000000);
    eventReceiver.unhookEvent(&eventSource);
    // Событие генерируется, но обработчики уже не вызываются
    __raise eventSource.MyEvent(10000, 1000000);
}

При запуске функции main на консоли будет выведено:

MyEventHandler2 asserts that Uncyclopedia is a funny encyclopedia with 1000000 articles and 100000000 words.
MyEventHandler1 asserts that Wikipedia is an important encyclopedia with 1000000 articles and 100000000 words.
MyEventHandler2 asserts that Uncyclopedia is a funny encyclopedia with 100000 articles and 10000000 words.
MyEventHandler1 asserts that Wikipedia is an important encyclopedia with 100000 articles and 10000000 words.

Примечания