Разработка на платформе «1С:Предприятие 8» зачастую кажется обособленной от классического объектно-ориентированного программирования (ООП). Многие базовые паттерны проектирования, описанные знаменитой «Бандой четырех» (Gang of Four / GoF), в 1С имеют свою специфику или встроены непосредственно в ядро платформы. Сегодня мы детально разберем один из самых частых вопросов, возникающих на стыке академической теории программирования и реальной практики 1С: является ли механизм подписки на события паттерном «Наблюдатель» (Observer)?
В этой статье мы подробно проанализируем архитектурную суть подписок в 1С, сопоставим их с классическим паттерном Observer, выясним, почему эта связь ближе к шаблону Publish-Subscribe (Pub-Sub), и научимся реализовывать настоящий динамический объектный Observer на платформе 1С.
Для начала вспомним классическое определение. Паттерн Наблюдатель (Observer) создает связь «один ко многим» между объектами таким образом, что при изменении состояния одного объекта (Субъекта / Издателя) все связанные с ним объекты (Наблюдатели / Подписчики) автоматически получают уведомление и обновляют свое состояние.
Ключевые признаки классического GoF-паттерна Observer:
Attach(Observer) и Detach(Observer).Update()).Теперь давайте спроецируем эти правила на реализацию подписок на события в экосистеме 1С.
Рассмотрим стандартный механизм метаданных ПодпискаНаСобытие в 1С. Например, мы хотим выполнять определенные действия при проведении любого документа. Мы создаем подписку в конфигураторе, указываем источник (документы) и обработчик (процедуру в общем модуле).
Давайте сопоставим этот механизм с классическим паттерном:
Вывод: Декларативная «Подписка на события» в 1С — это не классический объектный Observer, а его архитектурная эволюция в сторону событийно-ориентированной архитектуры (EDA) и паттерна Pub-Sub. Это сделано намеренно для обеспечения слабой связанности (loose coupling) модулей системы.
Если в бизнес-логике на сервере платформа предлагает нам статический Pub-Sub, то на клиенте (в интерфейсной части) мы имеем дело с чистейшим классическим паттерном Наблюдатель. Рассмотрим эти инструменты подробнее.
Это классический динамический Event Dispatcher / Observer. Схема работы выглядит следующим образом:
Оповестить(ИмяСобытия, Параметры, Источник).ОбработкаОповещения(ИмяСобытия, Параметр, Источник).Метод ОповеститьОбИзменении(Ссылка) сообщает платформе и всем активным динамическим спискам на формах, что данные конкретного объекта базы данных были изменены. Платформа здесь самостоятельно реализует логику Наблюдателя: формы, отображающие этот объект, ловят оповещение и автоматически инициируют перечитывание данных с сервера, обновляя интерфейс пользователя.
С переходом на асинхронную модель работы в управляемых формах повсеместно применяется объект ОписаниеОповещения. Хотя концептуально это ближе к механизму обратного вызова (Callback), это также является развитием идеи наблюдателя, когда вызывающий код заранее передает ссылку на процедуру, которая должна отреагировать на событие в будущем (например, при закрытии модального окна).
Что делать, если перед нами стоит задача спроектировать сложный интерактивный интерфейс (например, Автоматизированное рабочее место — АРМ), где бизнес-логика должна быть строго отделена от визуального представления, а формы должны динамически подписываться на изменения модели данных?
Поскольку в 1С нет классов в привычном понимании ООП, роль «классов» и «объектов» в объектной модели 1С могут успешно выполнять Внешние обработки или обычные Обработки конфигурации.
Давайте пошагово реализуем классический паттерн Наблюдатель.
Создадим обработку с именем ШаблонНаблюдателя. В модуле объекта этой обработки определим экспортный метод, который будут вызывать наши издатели:
// Модуль объекта Обработка.ШаблонНаблюдателя
// Принимает уведомление от субъекта об изменении состояния
Процедура ОбновитьСостояние(Издатель, ДополнительныеПараметры) Экспорт
// Конкретная логика реакции на событие реализуется в наследниках
ВызватьИсключение "Метод должен быть переопределен в конкретном наблюдателе!";
КонецПроцедуры
Создадим обработку Издатель. В модуле объекта этой обработки мы организуем динамический массив для хранения ссылок на наши объекты-наблюдатели и методы управления подпиской.
// Модуль объекта Обработка.Издатель
Перем СписокНаблюдателей;
// Инициализация коллекции подписчиков
Процедура Инициализировать() Экспорт
СписокНаблюдателей = Новый Массив;
КонецПроцедуры
// Динамическое добавление наблюдателя
Процедура Подписать(Наблюдатель) Экспорт
Если СписокНаблюдателей = Неопределено Тогда
Инициализировать();
КонецЕсли;
// Проверяем, нет ли уже такого подписчика в массиве
Если СписокНаблюдателей.Найти(Наблюдатель) = Неопределено Тогда
СписокНаблюдателей.Добавить(Наблюдатель);
КонецЕсли;
КонецПроцедуры
// Динамическое удаление наблюдателя
Процедура Отписать(Наблюдатель) Экспорт
Если СписокНаблюдателей = Неопределено Тогда
Возврат;
КонецЕсли;
Индекс = СписокНаблюдателей.Найти(Наблюдатель);
Если Индекс <> Неопределено Тогда
СписокНаблюдателей.Удалить(Индекс);
КонецЕсли;
КонецПроцедуры
// Оповещение всех зарегистрированных подписчиков
Процедура ОповеститьНаблюдателей(ДополнительныеПараметры = Неопределено) Экспорт
Если СписокНаблюдателей = Неопределено Тогда
Возврат;
КонецЕсли;
Для Каждого ТекущийНаблюдатель Из СписокНаблюдателей Цикл
Попытка
// Динамический вызов метода объекта-наблюдателя
ТекущийНаблюдатель.ОбновитьСостояние(ЭтотОбъект, ДополнительныеПараметры);
Исключение
// Здесь можно обработать ситуацию, если объект-наблюдатель был уничтожен
ЗаписьЖурналаРегистрации("Паттерн.Наблюдатель",
УровеньЖурналаРегистрации.Предупреждение,
,
,
"Не удалось оповестить наблюдателя: " + ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Теперь посмотрим, как связать эти объекты в единую работающую систему. Предположим, у нас есть несколько визуальных форм или других обработок, которые хотят динамически следовать за изменениями нашего Издателя.
// Пример использования паттерна в клиентском коде
// 1. Создаем объект-издатель
ОбъектИздатель = Обработки.Издатель.Создать();
ОбъектИздатель.Инициализировать();
// 2. Создаем конкретных наблюдателей (ими могут быть формы или другие обработки)
НаблюдательЛогирования = Обработки.НаблюдательЛог.Создать();
НаблюдательИнтерфейса = Обработки.НаблюдательЭкран.Создать();
// 3. Динамически подписываем их на события издателя
ОбъектИздатель.Подписать(НаблюдательЛогирования);
ОбъектИздатель.Подписать(НаблюдательИнтерфейса);
// 4. Происходит какое-то бизнес-событие в издателе
// Все подписчики автоматически получат уведомление и выполнят свой код
ОбъектИздатель.ОповеститьНаблюдателей("Данные успешно обновлены!");
// 5. При необходимости мы можем динамически отписать один из объектов
ОбъектИздатель.Отписать(НаблюдательЛогирования);
Используя этот подход, мы получаем классический, гибкий объектный паттерн Observer, полностью независимый от структуры метаданных и конфигуратора. Это незаменимо при разработке сложных тиражируемых решений со сложными интерфейсными сценариями.
Для закрепления материала структурируем особенности реализации паттерна в 1С:
Pub-Sub (Publish-Subscribe). Она статична, декларируется в конфигураторе, обрабатывается на сервере через общие модули. Идеальна для сквозного расширения логики проведения, записи или удаления документов без изменения их кода.Observer на клиенте. Отлично подходит для синхронизации состояния открытых форм пользователей в реальном времени.Понимание этих тонкостей архитектуры позволяет 1С-разработчикам писать чистый, поддерживаемый код, успешно совмещая современные стандарты проектирования ПО с мощными возможностями платформы «1С:Предприятие».