Библиотека для создания оберток (декораторов) над классами.
Декоратор повторяет публичный интерфейс класса (экспортные методы и поля), делегируя вызов вложенному реальному объекту.
Возможности:
- создание декоратора с идентичным интерфейсом;
- синхронизация значений полей декоратора и реального объекта;
- добавление методов, импортов, полей, шагов инициализации в runtime;
- добавление перехватчиков ("перед", "после", "вместо") вызова метода;
- объединение слоёв: декоратор над декоратором строится сразу над реальным объектом.
docs - todo;
#Использовать decorator
КакойТоКласс = Новый КакойТоКласс();
КакойТоКласс.Поле = 123;
Поле = Новый Поле("НовоеПоле")
.Публичное()
.ЗначениеПоУмолчанию("ЗначениеПоУмолчанию");
Метод = Новый Метод("ДобавленнаяФункция")
.Публичный()
.ТелоМетода("Возврат Поле + 1;");
Перехватчик = Новый Перехватчик("ДобавленнаяФункция")
.ТипПерехватчика(ТипыПерехватчиковМетода.Перед)
.Тело("Сообщить(""Привет, мир!"")");
Декоратор = Новый ПостроительДекоратора(КакойТоКласс)
.Импорт(Новый Импорт("fs"))
.Поле(Поле)
.Метод(Метод)
.Перехватчик(Перехватчик)
.Построить();
Ожидаем.Что(Декоратор.Поле).Равно(123);
Ожидаем.Что(Декоратор.РоднаяФункцияВозвращающаяИстина()).ЭтоИстина();
Ожидаем.Что(Декоратор.ДобавленнаяФункция()).Равно(124);Декоратор над декоратором не вкладывается: построитель добирается до реального объекта и переносит внутрь изменения развёрнутых слоёв. Цепочка вызовов остаётся одноуровневой, а поля, методы, перехватчики, импорты и шаги инициализации всех слоёв оказываются в одном модуле.
КакойТоКласс = Новый КакойТоКласс();
КакойТоКласс.Поле = 123;
Внутренний = Новый ПостроительДекоратора(КакойТоКласс)
.Поле(Новый Поле("СчётчикВызовов").Публичное().ЗначениеПоУмолчанию(0))
.Перехватчик(Новый Перехватчик("РоднаяФункцияВозвращающаяИстина")
.ТипПерехватчика(ТипыПерехватчиковМетода.Перед)
.Тело("СчётчикВызовов = СчётчикВызовов + 1;"))
.Построить();
Внешний = Новый ПостроительДекоратора(Внутренний)
.Метод(Новый Метод("Удвоить").Публичный().ТелоМетода("Возврат Поле * 2;"))
.Построить();
Внешний.РоднаяФункцияВозвращающаяИстина();
// Поле и перехватчик внутреннего слоя оказались во внешнем декораторе,
// а вызов пошёл сразу в КакойТоКласс, минуя Внутренний
Ожидаем.Что(Внешний.СчётчикВызовов).Равно(1);
Ожидаем.Что(Внешний.Удвоить()).Равно(246);Правила слияния:
- поля и методы слоёв добавляются после полей и методов реального объекта;
- импорты объединяются, повторы отбрасываются;
- шаги инициализации выполняются от внутреннего слоя к внешнему;
- перехватчики "до" (
Перед,ПередСинхронизациейПолей) отрабатывают от внешнего слоя к внутреннему, "после" (После,ПослеВызоваИсключения) — наоборот; - перехватчик
Вместозаменяет всё, что внутри него: перехватчики внутренних слоёв и сам метод выполняются, только если он вызвалДекоратор_ПродолжитьВызов(), как было бы с вложенными декораторами; - одинаковое имя поля или метода в двух слоях, либо совпадение с именем у реального объекта или со служебным полем декоратора — исключение при построении.
Развёрнутый декоратор в цепочке вызовов больше не участвует: его состояние копируется в построенный декоратор один раз, дальше объекты живут независимо.
Внутри перенесённых тел Декоратор_ИсходныйОбъект всегда указывает на реальный
объект. Обращение вида Декоратор_ИсходныйОбъект.<метод соседнего слоя> до
объединения работало, а после — сломалось бы, поэтому построитель ловит его и
выбрасывает исключение с подсказкой использовать ЭтотОбъект.<метод>. Код при этом
разбирается по правилам языка: регистр и пробелы вокруг точки не важны, комментарии
и строковые литералы не просматриваются. Вызов через промежуточную переменную проверка
не увидит.
ОбъединятьСлои(Ложь) означает и "сам не разворачиваю то, что мне передали",
и "не даю развернуть себя": построенный декоратор не оставляет рецепта, поэтому
следующий слой вложит его, как в версии 2.x.
Внутренний = Новый ПостроительДекоратора(КакойТоКласс)
.ОбъединятьСлои(Ложь)
.Построить();
Внешний = Новый ПостроительДекоратора(Внутренний).Построить();
// Внешний -> Внутренний -> КакойТоКлассДля того, что бы работал отладчик и заходил в добавленные методы нужно: Либо установить любое значение переменной окружения с именем OSCRIPT_DECORATOR_DEBUG. Либо вызвать у построителя метод Отладка()
Декоратор = Новый ПостроительДекоратора(КакойТоКласс)
.Импорт(Новый Импорт("fs"))
.Поле(Поле)
.Метод(Метод)
.Перехватчик(Перехватчик)
.Отладка()
.Построить();Регистрировать типы и создавать их экземпляры можно из нескольких фоновых заданий сразу. Реестр зарегистрированных типов общий для процесса и защищён блокировкой, регистрации идут по очереди. Создание экземпляра зарегистрированного типа ждёт, пока идущая регистрация закончится.
Модуль типа регистрация пишет во временный файл и удаляет его сразу после подключения.
Временные файлы, созданные другим кодом через ВременныеФайлы, она не трогает.
// Конструктор
//
// Параметры:
// Объект - Произвольный - Инстанс объекта, над которым нужно создать декоратор
// - Тип - Тип объекта, инстанс которого нужно создать и задекорировать
// - Неопределено - Декоратор создаётся с нуля
//
Процедура ПриСозданииОбъекта(Объект = Неопределено)// Добавляет в декоратор новое поле
//
// Параметры:
// Поле - Поле - Добавляемое поле
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция Поле(Поле) Экспорт// Добавляет в декоратор новый метод.
//
// Параметры:
// Метод - Метод - Добавляемый метод.
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция Метод(Метод) Экспорт// Добавляет в декоратор перехватчик
//
// Параметры:
// Перехватчик - Перехватчик - Добавляемый перехватчик
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция Перехватчик(Перехватчик) Экспорт// Добавляет в декоратор импорт библиотеки (#Использовать).
//
// Параметры:
// Импорт - Импорт - Импорт библиотеки
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция Импорт(Импорт) Экспорт// Добавляет в декоратор шаг инициализации в тело модуля.
//
// Параметры:
// ШагИнициализации - ШагИнициализации - Выполняемый шаг инициализации
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция ШагИнициализации(ШагИнициализации) Экспорт// Добавляет значение параметра для инициализации объекта, в случае создания декоратора из типа
//
// Параметры:
// ЗначениеПараметра - Произвольный - Значение параметра конструктора по типу
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция ЗначениеПараметраКонструктораПоТипу(ЗначениеПараметра) Экспорт// Управляет объединением слоёв вложенных декораторов.
//
// При включённом объединении декоратор над декоратором строится сразу над
// исходным объектом, а изменения развёрнутых слоёв переносятся внутрь.
// При выключенном декоратор вкладывается, и построенный объект не оставляет
// рецепта, поэтому и следующий слой развернуть его не сможет.
//
// Параметры:
// Включено - Булево - Объединять слои
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция ОбъединятьСлои(Включено = Истина) Экспорт// Разрешает построение декоратора без инстанса исходного объекта.
// Действует в режиме создания из типа: если конструктор типа выбрасывает исключение,
// построение не прерывается, а декоратор строится без инстанса (Неопределено).
// Интерфейс типа при этом полностью сохраняется, но делегирование вызовов
// в исходный объект и синхронизация полей недоступны.
//
// Параметры:
// Допустить - Булево - Признак допустимости отсутствия инстанса
//
// Возвращаемое значение:
// ПостроительДекоратора - Ссылка на текущий инстанс ПостроительДекоратора
//
Функция ДопуститьОтсутствиеИнстанса(Допустить = Истина) ЭкспортПример — декоратор над типом, который нельзя создать через конструктор (например, HTTPОтвет):
Перехватчик = Новый Перехватчик("ПолучитьТелоКакСтроку")
.ТипПерехватчика(ТипыПерехватчиковМетода.Перед)
.Тело("Возврат ""Переопределенный ответ"";");
Декоратор = Новый ПостроительДекоратора(Тип("HTTPОтвет"))
.ДопуститьОтсутствиеИнстанса()
.Перехватчик(Перехватчик)
.Построить();// Получить сконструированный текст сценария будущего декоратора
//
// Возвращаемое значение:
// Строка - Текст сценария декоратора
//
Функция ТекстСценария() Экспорт// Сконструировать готовый декоратор по настройкам конструктора Декоратор.
//
// Параметры:
// ТекстСценария - Строка - Текст сценария декоратора. Если не задан, формируется автоматически.
//
// Возвращаемое значение:
// Произвольный - Декоратор над объектом, переданным в конструктор Декоратор
//
Функция Построить(ТекстСценария = Неопределено) Экспорт// Сконструировать готовый декоратор по настройкам конструктора Декоратор,
// и зарегистрировать его как новый тип в системе типов
//
// Параметры:
// ИмяТипа - Строка - Имя, под которым тип будет зарегистрирован в системе
//
Процедура ЗарегистрироватьВСистемеТипов(ИмяТипа) Экспорт// Конструктор
//
// Параметры:
// ИмяАннотации - Строка - Имя добавляемой аннотации
//
Процедура ПриСозданииОбъекта(ИмяАннотации)// Добавляет параметр аннотации, допускается указание одного безымянного параметра
//
// Параметры:
// Значение - Строка, Число, Булево, Дата - Значение параметра
// Имя - Строка - Необязательный, имя параметра
//
// Возвращаемое значение:
// Аннотация - Ссылка на текущий инстанс Аннотация
//
Функция Параметр(Значение, Имя = "") Экспорт// Конструктор
//
// Параметры:
// ПодключаемаяБиблиотека - Строка - Имя или путь подключаемой библиотеки
//
Процедура ПриСозданииОбъекта(ПодключаемаяБиблиотека)// Устанавливает тип подключения библиотеки
//
// Параметры:
// УстанавливаемыйТипПодключения - ТипыПодключенияБиблиотек - Устанавливаемый тип подключения библиотеки
//
// Возвращаемое значение:
// Импорт - Ссылка на текущий инстанс Импорт
//
Функция ТипПодключения(УстанавливаемыйТипПодключения) Экспорт// Конструктор
//
// Параметры:
// ИмяМетода - Строка - Имя создаваемого метода
//
Процедура ПриСозданииОбъекта(ИмяМетода)// Добавляет аннотацию метода
//
// Параметры:
// Аннотация - Аннотация - Добавляемая аннотация
//
// Возвращаемое значение:
// Метод - Ссылка на текущий инстанс Метод
//
Функция Аннотация(Аннотация) Экспорт// Устанавливает тело метода
//
// Параметры:
// ТелоМетода - Строка - Устанавливаемое тело метода
//
// Возвращаемое значение:
// Метод - Ссылка на текущий инстанс Метод
//
Функция ТелоМетода(ТелоМетода) Экспорт// Добавляет параметр метода
//
// Параметры:
// Параметр - ПараметрМетода - Добавляемый параметр метода
//
// Возвращаемое значение:
// Метод - Ссылка на текущий инстанс Метод
//
Функция Параметр(Параметр) Экспорт// Устанавливает экспорт метода
//
// Возвращаемое значение:
// Метод - Ссылка на текущий инстанс Метод
//
Функция Публичный() Экспорт// Устанавливает признак того что этот метод Процедура
//
// Возвращаемое значение:
// Метод - Ссылка на текущий инстанс Метод
//
Функция ЭтоПроцедура() Экспорт// Конструктор
//
// Параметры:
// ИмяПараметра - Строка - Имя параметра метода
//
Процедура ПриСозданииОбъекта(ИмяПараметра)// Устанавливает Знач параметра
//
// Возвращаемое значение:
// ПараметрМетода - Ссылка на текущий инстанс ПараметрМетода
//
Функция ПоЗначению() Экспорт// Устанавливает значение по умолчанию для параметра
//
// Параметры:
// ЗначениеПоУмолчаниюПараметра - Строка, Число, Дата, Булево, Неопределено - Значение параметра по умолчанию
//
// Возвращаемое значение:
// ПараметрМетода - Ссылка на текущий инстанс ПараметрМетода
//
Функция ЗначениеПоУмолчанию(ЗначениеПоУмолчаниюПараметра) Экспорт// Добавляет аннотацию параметра
//
// Параметры:
// Аннотация - Аннотация - Добавляемая аннотация
//
// Возвращаемое значение:
// ПараметрМетода - Ссылка на текущий инстанс ПараметрМетода
//
Функция Аннотация(Аннотация) Экспорт// Конструктор
//
// Параметры:
// ИмяПерехватываемогоМетода - Строка - Имя метода для которого предназначен перехватчик
//
Процедура ПриСозданииОбъекта(ИмяПерехватываемогоМетода)// Устанавливает тело перехватчика
//
// Параметры:
// ТелоПерехватчика - Строка - Устанавливаемое тело перехватчика
//
// Возвращаемое значение:
// Перехватчик - Ссылка на текущий инстанс Перехватчик
//
Функция Тело(ТелоПерехватчика) Экспорт// Устанавливает тип перехватчика
//
// Параметры:
// ТипПерехватчика - ТипыПерехватчиковМетода - Устанавливаемое тип перехватчика
//
// Возвращаемое значение:
// Перехватчик - Ссылка на текущий инстанс Перехватчик
//
Функция ТипПерехватчика(ТипПерехватчика) Экспорт// Конструктор
//
// Параметры:
// ИмяПоля - Строка - Имя поля
//
Процедура ПриСозданииОбъекта(ИмяПоля)// Устанавливает Экспорт поля
//
// Возвращаемое значение:
// Поле - Ссылка на текущий инстанс Поле
//
Функция Публичное() Экспорт// Устанавливает значение по умолчанию для поля
//
// Параметры:
// УстанавливаемоеЗначениеПоУмолчанию - Произвольный - Значение поля по умолчанию
//
// Возвращаемое значение:
// Поле - Ссылка на текущий инстанс Поле
//
Функция ЗначениеПоУмолчанию(УстанавливаемоеЗначениеПоУмолчанию) Экспорт// Добавляет аннотацию поля
//
// Параметры:
// Аннотация - Аннотация - Добавляемая аннотация
//
// Возвращаемое значение:
// Поле - Ссылка на текущий инстанс Поле
//
Функция Аннотация(Аннотация) Экспорт// Конструктор
//
// Параметры:
// ТелоШага - Строка - тело шага инициализации
//
Процедура ПриСозданииОбъекта(ТелоШага)Перехватчик вызывается перед выполнением перехватываемого метода
Перехватчик выполняется вместо перехватываемого метода, как &Вместо в расширениях 1С.
Он отрабатывает после перехватчиков Перед своего слоя; исходный метод и перехватчики
внутренних слоёв выполняются, только если тело вызвало Декоратор_ПродолжитьВызов().
- Тело пишется как тело метода: у функции результат возвращается через
Возврат, без него функция вернётНеопределено; у процедурыВозврат;просто завершает перехватчик.Возвратне пропускает ниПосле, ни синхронизацию полей — они видят результат вДекоратор_ВозвращаемоеИзМетодаЗначение, как обычно. Декоратор_ПродолжитьВызов()вызывает то, что внутри перехватчика, с текущими значениями параметров метода. У функции это выражение, оно возвращает результат; у процедуры — только инструкцияДекоратор_ПродолжитьВызов();. Параметры можно поменять до вызова, результат — после, вызов можно повторить или обернуть вПопытка.- Метода
Декоратор_ПродолжитьВызовв модуле нет: построитель заменяет его вызов в коде тела. Регистр и пробелы у скобок не важны, строки и комментарии не затрагиваются. - Исключение из тела обрабатывается как исключение вызова: отрабатывают
ПослеВызоваИсключения. - Несколько перехватчиков
Вместона одном методе вкладываются друг в друга: первый зарегистрированный — внешний.
Кэширование результата:
Декоратор = Новый ПостроительДекоратора(КакойТоКласс)
.Поле(Новый Поле("Кэш"))
.Перехватчик(Новый Перехватчик("ТяжёлаяФункция")
.ТипПерехватчика(ТипыПерехватчиковМетода.Вместо)
.Тело("Если Кэш = Неопределено Тогда
| Кэш = Декоратор_ПродолжитьВызов();
|КонецЕсли;
|Возврат Кэш;"))
.Построить();Продолжение вызывается только синхронно. Чтобы выполнить метод в другом потоке, слою нужен
отдельный объект, в который можно войти снаружи: постройте его с ОбъединятьСлои(Ложь) поверх
остальных слоёв и вызывайте из фонового задания Декоратор_ИсходныйОбъект — вызов пройдёт
через перехватчики всех внутренних слоёв.
Перехватчик вызывается после выполнения перехватываемого метода
Перехватчик вызывается если перехватываемый метод упал в исключение, в момент перед пробросом исключения
Перехватчик вызывается в самом начале, перед синхронизацией полей декоратора и оригинального метода
Библиотека подключается по имени:
#Использовать fsБиблиотека подключается по пути к библиотеке:
#Использовать "./libs/myLib"// Проверяет, что объект является декоратором, построенным этой библиотекой.
//
// Параметры:
// Объект - Произвольный - Проверяемый объект
//
// Возвращаемое значение:
// Булево - Истина, если объект - декоратор
//
Функция ЭтоДекоратор(Объект) Экспорт// Получает объект, вокруг которого построен декоратор.
//
// Параметры:
// Декоратор - Произвольный - Декоратор, у которого нужно получить обёрнутый объект
//
// Возвращаемое значение:
// - Произвольный - обёрнутый объект
// - Неопределено - объект не является декоратором либо декоратор построен
// без исходного объекта
//
Функция ИсходныйОбъект(Декоратор) Экспорт// Получает тип объекта, вокруг которого построен декоратор (рекурсивно).
//
// Параметры:
// Декоратор - Произвольный - Объект, у которого нужно найти исходный тип объекта
//
// Возвращаемое значение:
// Тип - Исходный тип объекта
//
Функция ИсходныйТип(Декоратор) Экспорт// Синхронизировать значения экспортных полей двух объектов.
//
// Параметры:
// ИсходныйОбъект - Произвольный - Источник значений свойств
// Потребитель - Произвольный - Получатель значений свойств
//
Процедура СинхронизироватьПоля(ИсходныйОбъект, Потребитель) Экспорт// Возвращает имя, которое получит метод исходного объекта в декораторе.
// Имена, совпадающие с зарезервированными, получают префикс "_".
//
// Параметры:
// ИмяМетода - Строка - Имя метода исходного объекта
//
// Возвращаемое значение:
// Строка - Имя метода в декораторе
//
Функция ИмяМетодаДекоратора(Знач ИмяМетода) Экспорт// Возвращает имя, которое получит параметр метода исходного объекта в декораторе.
// Имена, совпадающие с ключевыми словами языка (например, val у Структура.Вставить),
// получают префикс "_".
//
// Параметры:
// ИмяПараметра - Строка - Имя параметра метода исходного объекта
//
// Возвращаемое значение:
// Строка - Имя параметра в декораторе
//
Функция ИмяПараметраДекоратора(Знач ИмяПараметра) Экспорт// Имя поля, в котором хранится ссылка на исходный объект.
Перем Поле_ИнстансОбъекта Экспорт;
// Имя поля, в котором хранится лог декоратора.
Перем Поле_Лог Экспорт;
// Имя поля, в котором хранится рецепт декоратора - массив слоёв изменений,
// от внутреннего слоя к внешнему.
Перем Поле_Рецепт Экспорт;
// Массив имён служебных полей декоратора.
Перем ИменаСлужебныхПолей Экспорт;Всё, кроме первого пункта, откатывается вызовом ОбъединятьСлои(Ложь) у построителя.
- Удалён класс
КонструкторДекоратораи весь API версии 1. ИспользуйтеПостроительДекоратора. Декоратор_ИсходныйОбъекттеперь всегда содержит реальный объект, а не соседний декоратор.- Переданный в конструктор декоратор в цепочке вызовов больше не участвует: его состояние копируется один раз при построении, дальше объекты живут независимо.
- Блок
Попыткаохватывает только вызов метода реального объекта. РаньшеПопыткавнешнего декоратора охватывала всю обёртку внутреннего, поэтому исключение из перехватчика внутреннего слоя запускалоПослеВызоваИсключениявнешнего. Теперь нет. - Конфликт имён полей и методов между слоями, с реальным объектом или со служебными полями декоратора — исключение при построении вместо ошибки компиляции сценария.
- Обращение
Декоратор_ИсходныйОбъект.<метод соседнего слоя>в теле метода, перехватчика или шага инициализации — исключение при построении.
Замеры построения декоратора, вызова его метода и создания экземпляра зарегистрированного типа
лежат в каталоге benchmarks: один или три слоя, в каждом — без перехватчиков, с Перед и После
или ещё и с Вместо. Запускаются
библиотекой benchmark. В зависимости разработки
она не входит: ей нужен OneScript 1.9.4, а декоратору хватает 1.9.2. Поставьте её отдельно:
opm install -l benchmark
oscript tasks/benchmark.os- Замеряется decorator из репозитория, а не пакет из
oscript_modules, который benchmark подтягивает через свои зависимости:benchmarks/oscript.cfgподключаетbenchmarks/lib, гдеdecoratorперенаправлен на корень репозитория. Если перенаправление не сработает, замер упадёт, а не пойдёт молча по чужой версии. tasks/benchmark.osотключает профилирование многоуровневой компиляции .NET (DOTNET_TieredPGO=0): с ним первые кейсы в процессе выходят завышенными и с большим разбросом.- Абсолютные значения сильно зависят от загрузки машины. Сравнивайте версии прогонами подряд.