Расширения желудей ОСени по образу расширений 1С
Пакет лежит в основном пуле хаба, а opm знает этот хаб по умолчанию: короткой формы достаточно.
opm install autumn-extensionСкачанный файл ставится командой opm install -f <файл> — сеть для этого уже не нужна.
Описание
autumn-extension
Расширения желудей ОСени по образу расширений конфигурации 1С:
отдельный класс перехватывает методы чужого желудя аннотациями &Перед, &После и &Вместо, не трогая его код.
Построено на decorator и его перехватчике Вместо (decorator 3.1.0 и новее).
Установка и подключение
opm install autumn-extension
Подключите библиотеку в приложении рядом с ОСенью - больше ничего настраивать не нужно, аннотации и напильники расширений поделка найдёт сама:
#Использовать autumn
#Использовать autumn-extension
Использование
Есть желудь, поведение которого нужно поправить, не меняя его код:
// Касса.os
Функция Оплатить(Сумма) Экспорт
Возврат "чек " + Сумма;
КонецФункции
&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Расширение - отдельный класс приложения с аннотацией &Расширение над конструктором. В её параметре - имя расширяемого
желудя. Экспортные методы расширения с аннотациями &Перед, &После или &Вместо перехватывают метод желудя, имя
которого указано в аннотации; сами методы расширения можно называть как угодно:
// ПроверкаОплаты.os
&Перед("Оплатить")
Процедура ПроверитьСумму(Сумма) Экспорт
Если Сумма <= 0 Тогда
ВызватьИсключение "Сумма оплаты должна быть положительной";
КонецЕсли;
КонецПроцедуры
&После("Оплатить")
Процедура СообщитьОбОплате(Сумма) Экспорт
Сообщить("Оплачено: " + Сумма);
КонецПроцедуры
&Расширение("Касса")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Всё. Любой, кто получит Касса из поделки - через &Пластилин или Поделка.НайтиЖелудь("Касса"), - получит её уже
с расширением:
Касса = Поделка.НайтиЖелудь("Касса");
Касса.Оплатить(100); // выведет "Оплачено: 100" и вернёт "чек 100"
Касса.Оплатить(0); // исключение "Сумма оплаты должна быть положительной", метод кассы не выполнится
Расширение само является желудем: в него можно прилеплять другие желуди и детальки, у него могут быть &Порядок
и &Прозвище. Отдельно помечать его &Желудь не нужно - &Расширение делает это само.
Как в 1С, расширение - часть расширяемого желудя и живёт вместе с ним: у желудя-одиночки оно одно, у компанейского -
своё у каждого экземпляра, со своими переменными модуля. Поэтому &Характер у расширения не указывается. Общее для всех
экземпляров состояние держите в прилепленном желуде-одиночке, как в общем модуле.
&Вместо и продолжение вызова
Метод с &Вместо выполняется вместо метода желудя. Чтобы вызвать метод желудя, добавьте методу расширения последний
параметр - продолжение вызова. Это Действие, поэтому там, где в 1С пишут ПродолжитьВызов(...), здесь -
ПродолжитьВызов.Выполнить(...):
// КэшКалькулятора.os
Перем Кэш;
&Вместо("Посчитать")
Функция ПосчитатьСКэшем(А, Б, ПродолжитьВызов) Экспорт
Ключ = СтрШаблон("%1:%2", А, Б);
Если Кэш[Ключ] = Неопределено Тогда
Кэш[Ключ] = ПродолжитьВызов.Выполнить(А, Б);
КонецЕсли;
Возврат Кэш[Ключ];
КонецФункции
&Расширение("Калькулятор")
Процедура ПриСозданииОбъекта()
Кэш = Новый Соответствие;
КонецПроцедуры
- Имя параметра - любое,
ПродолжитьВызов- чтобы читалось как в 1С. - Без этого параметра метод расширения заменяет метод желудя целиком: метод желудя не выполняется вовсе.
- Продолжение проходит через расширения, применённые раньше этого, и через сам метод желудя.
- У процедуры продолжение - инструкция:
ПродолжитьВызов.Выполнить(ВРег(Текст));. - Параметры передаются по ссылке в обе стороны: если метод желудя изменит параметр, расширение увидит новое значение.
- Пропущенные параметры (
ПродолжитьВызов.Выполнить(А),ПродолжитьВызов.Выполнить(А, , В)) метод желудя заполнит своими значениями по умолчанию. - Продолжение можно вызвать несколько раз или с другими значениями параметров.
Результат функции в &После
Если перехватываемый метод - функция, у метода с &После может быть ещё один, последний параметр - результат функции.
Он передаётся по ссылке, присвоенное ему значение станет результатом:
&После("Посчитать")
Процедура ПослеПосчитать(А, Б, Результат) Экспорт
Результат = Окр(Результат, 2);
КонецПроцедуры
Новые методы
Экспортный метод расширения без &Перед, &После и &Вместо становится методом желудя, как будто объявлен в нём:
// КэшКалькулятора.os
Функция РазмерКэша() Экспорт
Возврат Кэш.Количество();
КонецФункции
// где угодно
Поделка.НайтиЖелудь("Калькулятор").РазмерКэша();
- Сигнатура - параметры,
Знач, значения по умолчанию - и аннотации метода переносятся на желудь, поэтому их видят напильники, которые применяются после расширений, например&Замеряемыйиз autumn-opentelemetry. - Если у желудя уже есть метод с таким именем, это ошибка: чтобы изменить метод желудя, перехватите его.
- Расширения, применённые позже, могут перехватывать добавленные методы как обычные методы желудя.
Параметры
Параметры метода расширения - те же, что у перехватываемого метода (плюс последний параметр, где он описан выше),
и передаются по ссылке: присваивание параметру в &Перед дойдёт до метода желудя.
&Перед("Оплатить")
Процедура ОкруглитьСумму(Сумма) Экспорт
Сумма = Окр(Сумма, 2); // касса получит округлённую сумму
КонецПроцедуры
Значения по умолчанию берутся из метода желудя. Признак Знач у параметров должен совпадать.
Назначение и порядок применения
Как в 1С, у расширения есть назначение: Исправление, Адаптация или Дополнение (по умолчанию). Расширения
применяются в этом порядке, и каждое следующее оборачивает предыдущие: исправление ближе всех к методу желудя.
Дополнение.Перед
Адаптация.Вместо ─┐
Исправление.Перед
Касса.Оплатить
Исправление.После
Адаптация.Вместо ─┘ (после ПродолжитьВызов.Выполнить)
Дополнение.После
Каждое назначение применяет свой напильник, поэтому назначение - это ещё и место среди остальных напильников:
| Назначение | Напильник | &Порядок |
|---|---|---|
| Исправление | ОбработкаНапильникомРасширенияИсправление |
100 |
| Адаптация | ОбработкаНапильникомРасширенияАдаптация |
200 |
| Дополнение | ОбработкаНапильникомРасширенияДополнение |
300 |
Напильники с большим порядком оборачивают расширения: например, замеры autumn-opentelemetry (500-503) учитывают время
работы расширений. Напильник без &Порядок (порядок 1) применяется раньше расширений и оказывается внутри них.
Внутри одного назначения расширения применяются по &Порядок расширения, при равном порядке - по имени желудя.
&Расширение("Касса")
&Порядок(10)
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Аннотации
&Расширение
Размещается над конструктором класса.
| Параметр | Тип | Описание |
|---|---|---|
Значение |
Строка | Обязательный. Имя или прозвище расширяемого желудя. Расширение применяется ко всем желудям с таким именем или прозвищем |
Назначение |
Строка | Исправление, Адаптация или Дополнение (по умолчанию), см. модуль НазначенияРасширений |
Имя |
Строка | Имя желудя расширения. По умолчанию - имя типа |
Добавляет желудю &Желудь, &Прозвище("Расширение") и &Характер("Компанейский").
&Перед, &После, &Вместо
Размещаются над экспортными методами класса-расширения. Параметр - имя перехватываемого метода.
| Аннотация | Метод расширения | Параметры |
|---|---|---|
&Перед |
процедура | как у метода желудя |
&После |
процедура | как у метода желудя; у функции можно добавить последний параметр - результат |
&Вместо |
того же вида, что метод желудя | как у метода желудя; можно добавить последний параметр - продолжение вызова |
Одно расширение перехватывает метод одним из наборов: &Перед, &После, &Вместо или &Перед вместе с &После.
Один метод расширения перехватывает один метод.
Отличия от 1С
- Метод расширения должен быть экспортным. Расширение - отдельный объект, приватный метод из декоратора не вызвать.
- Продолжение вызова - не глобальная функция, а последний параметр метода с
&Вместо:ПродолжитьВызов.Выполнить(...)вместоПродолжитьВызов(...). Его можно вызвать несколько раз. &Переди&Последопустимы и для функций, а&Послеможет получить и заменить результат функции.- Метод, которого у желудя нет, перехватить нельзя - это ошибка, а не пустой обработчик.
- Расширить можно только экспортные методы желудя.
- Перехватываются только вызовы снаружи - через желудь, полученный из поделки. Вызов метода изнутри самого желудя
(
Оплатить()из другого метода кассы) идёт мимо расширений: декоратор оборачивает объект, а не его модуль. - Код расширения не видит методы и переменные желудя: модуль расширения компилируется отдельно, и
ЭтотОбъектв нём - само расширение. - Экспортные переменные расширения не становятся полями желудя - только методы.
Когда и что проверяется
Ошибки в самом классе расширения - неэкспортный метод, &Перед над функцией, недопустимый набор аннотаций, неизвестное
назначение, расширение самого себя - останавливают создание поделки.
Ошибки, для которых нужен расширяемый желудь - нет такого метода, не совпадают параметры или вид метода, - проверяются
при создании этого желудя. ОСень такие ошибки не выбрасывает, а пишет в лог oscript.lib.autumn.application.context
и возвращает вместо желудя Неопределено. Остальные желуди создаются как обычно.
Применение расширений пишется в лог oscript.lib.autumn.extension на уровне отладки.
Как это устроено
Каждое расширение - слой декоратора над желудем: перехватчики Перед, После и Вместо, которые вызывают методы
расширения, и методы, которые делегируют вызов новым методам расширения. Слои объединяются в один декоратор,
и вложенность расширений сохраняется: Вместо внешнего слоя заменяет всё, что внутри него.
Исключение - &Вместо с продолжением вызова. Продолжение - Новый Действие(<что построено до расширения>, "<метод>"),
а Действие, как и любой внешний код, вызывает только экспортные методы. Продолжение же перехватчика Вместо
в объединённом декораторе - приватный метод. Поэтому расширение, которое получает продолжение, строится отдельным
объектом (ОбъединятьСлои(Ложь)), а расширения, применённые после, снова объединяются поверх. &Вместо без продолжения
объединяется, как остальные.
Ограничения
- Не прилепляйте расширяемый желудь к его же расширению - это циклическая зависимость. Если он нужен, используйте
&Табакерка. - Желуди времени инициализации (
&Спецификация("Инициализация")) и сами напильники ОСень не обрабатывает напильниками, поэтому расширить их нельзя. - Лишние параметры в
ПродолжитьВызов.Выполнить(...)OneScript отвергает невнятной ошибкойIndex was outside the bounds of the array.
Разработка
Зависимости:
opm install -l --dev
Прогон тестов (OneUnit):
oscript_modules/bin/oneunit e