hub.oscript.io
hub.oscript.io
Войти

autumn-extensionОпубликовано доверенным конвейеромпакет

Расширения желудей ОСени по образу расширений 1С

автор: Кирилл Черненкоскачиваний: 6репозиторий: github.com/autumn-library/autumn-extension

Пакет лежит в основном пуле хаба, а 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