Autumn/ОСень
Что такое осень - это желудь!
Осень…. прекрасная пора. Ею вдохновлялся Пушкин, говорил, что осенью его особенно прёт. Именно это и произошло с авторами фреймворка "ОСень". Потому что осень - это прекрасно.
Когда приложение становится большим, вам потребуется дерево (желательно дуб) и немного пластилина. Ах, да, обязательно творческое осеннее настроение, чай и стабильная психика.
Фреймворк компонентных приложений для 1Script под названием "ОСень" поможет вам невероятным магическим образом компоновать ваше приложение из компонентов, которым не нужно заниматься собственным созданием или настройкой. И все это будет щедро обмазано пластилином и приятно пахнуть дубовыми вениками.
Оглавление
- Autumn/ОСень
- Оглавление
- Зачем мне это?
- Как с этим работать?
- Инициализация приложения
- Запуск приложения
- Объявление компонента
- Получение экземпляра компонента
- Связывание компонентов между собой
- Фабрика компонентов
- Внедрение зависимостей в поля и функции-сеттеры
- Внедрение настроек приложения
- Произвольные аргументы конструктора
- Алиасы компонентов
- Группировка компонентов по алиасу
- Приоритизация компонентов с общим алиасом
- Переопределение компонента по имени
- Пост-инициализация компонента
- Уникальность экземпляров компонентов
- Дополнительная обработка компонента
- Использование контекста приложения
- Вынос логики регистрации желудей из точки запуска приложения
- Инверсия управления запуском приложения
- Собственные аннотации
- Наследование аннотаций
- Обработка аннотаций
- Статический инициализатор контекста
- Заключение
Зачем мне это?
Вот есть у вас объект, у которого объявлен конструктор с параметрами. И есть много мест, где он создается. Параметры этого конструктора называются зависимостями. То есть, объект не может жить без передачи ему этих параметров, он от них зависит.
А где взять значения этих параметров, чтобы передать в объект? Очевидно что их тоже надо создать (через Новый
или откуда-то получить). А у них тоже есть зависимости, и у зависимостей зависимостей есть зависимости.
Зависимость. Да, вот слово, которое приходит в голову, когда изучаешь API "ОСени". Зависимость у авторов явно есть. Но это неправда. Мы зависимы только от просмотра зоопорно красивого кода и вообще, не одобряем нехороших веществ, разве что пластилин (не тот) и желуди. Даа, желуди нам определенно нравятся, ведь они овальные и в смешных шапочках.
Но! шутки в сторону. В ваших руках не просто пластилин и желуди, в ваших руках - Dependency Injection Framework для любимого языка программирования. Теперь ваши объекты будут опрятными и шелковистыми создаваться сами, и не нужно будет думать как добыть параметры конструирования, сколько их, в каком порядке они идут. Достаточно сказать: "Хочу класс УгольныйКомбайн" - а марка колес, двигатель и прочие запчасти КАК-ТО создадутся и передадутся в конструктор.
Теперь приложение может состоять из сложных компонентов, которые намного проще менять и перенастраивать. Вот был у вас класс ОбновляторВерсии1С
. В конструкторе получал логин/пароль пользователя. И все было хорошо, но понадобилось вам в этот класс добавить знание об уже установленных версиях 1С, чтобы не скачивать лишние с сайта. Можно прямо в этом классе написать проверку установленных версий, но это нарушение ПЕО (Принцип Единой Ответственности): проверятор версий не надо смешивать с обновлятором. Мало ли в каких еще местах пригодится проверятор версий, а мы его жестко внутрь другого класса зашьем… Повторное использование - наше все.
Чтобы всё было по красоте, нам надо передать в конструктор объект ПроверяторВерсий
, который предоставит Обновлятору1С
информацию о том, что за версии у нас уже установлены. И все бы ничего, но Обновлятор1С
создается через Новый
в тысяче мест. В эти места нужно залезть, и дополнительно там создать ПроверяторВерсий
и передать его в конструктор... А если Проверятор
тоже имеет зависимость (а кто ее не имеет в наше сложное время, а?), тогда придется протащить всё дерево зависимостей через все методы и компоненты чуть ли не от самого старта приложения. Так жить нельзя, такая жесткая связность будет мешать развитию приложения и усложнять его.
Осенью, когда вам хочется создать Обновлятор1С
, вы просто говорите "Лунная призма, дай мне силы!" Осень.НайтиЖелудь("Обновлятор1С")
- и все зависимости зависимостей создадутся сами. Сколько у них параметров в конструкторе, как они создаются, кто им передает параметры и где берет - это знает только "ОСень". К чему вдаваться в метафизику… "ОСень" возьмет ваши проблемы на себя, главное - показать ей, где лежит пластилин...
Как с этим работать?
Инициализация приложения
"ОСень" - это вам non penis canina est, а фреймворк. Любой порядочный фреймворк нужно немножечко сконфигурировать, чтобы дальше все заработало.
Для инициализации контекста "ОСени" служит класс Поделка
, который необходимо создать через Новый
(один разочек можно и написать это вредное слово), а затем наполнить Желудями, Дубами и Напильниками. Нет, мы не упоролись, скоро расскажем, что тут к чему.
Инициализировать контекст можно двумя способами.
- Через сканирование каталога:
// file: main.os
#Использовать autumn
Поделка = Новый Поделка();
Поделка.ПросканироватьКаталог(ТекущийКаталог());
Сей нехитрый код заставит ОСень просканировать все *.os
файлы в текущем каталоге (включая подкаталоги), понять, кто из них желудь, а кто дуб, и последовательно их добавить в контекст.
- Через непосредственную регистрацию:
// file: main.os
#Использовать autumn
Поделка = Новый Поделка();
Поделка
.ДобавитьЖелудь(Тип("ВерхнеуровневыйЖелудь"))
.ДобавитьЖелудь(Тип("ЖелудьНижнегоУровня"))
.ДобавитьДуб(Тип("ГлавныйДуб"));
Не так красиво, как первый вариант, зато гибкости побольше, если вдруг она необходима.
Запуск приложения
После добавления всех желудей, дубов и прочих частей в нашу поделку мы готовы к запуску приложения.
Поделка.ЗапуститьПриложение();
В чем суть? Жизненный цикл нашей поделки разделен на две фазы:
- фаза инициализации, когда мы можем только добавлять в поделку новые компоненты, но еще не разрешаем им взаимодействовать;
- фаза выполнения, когда в нашу поделку вдыхается жизнь
и выдыхаются желуди, наше приложение запускается и мы можем наконец начать делать что-то полезное.
Объявление компонента
Как вы, наверное, догадались по словосочетанию "компонентное приложение", основой вашего приложения становится Желудь
. Жёлудь - это всё, и всё есть жёлудь. ОбновляторВерсии1С
? Жёлудь. ПроверяторУстановленныхВерсий
? Тоже Жёлудь. И даже логин с паролем - это тоже в некотором роде жёлуди.
Основной способ обозначения класса как желудя - это навешивание аннотации &Желудь
над конструктором объекта. Вроде такого:
// file: Классы/ПроверяторВерсий.os
&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры
... прочая очень нужная, но абсолютно не интересная логика класса.
Главной характеристикой желудя является его имя. По умолчанию имя берется из имени типа (ПроверяторВерсий
для случая выше), но может быть переопределено в параметре аннотации &Желудь
.
Получение экземпляра компонента
Мы определили желудь, настало время его создать!
В создании компонентов нам поможет Поделка
. Зря что ли мы накидывали в нее желудей?
// file: main.os
ПроверяторВерсий = Поделка.НайтиЖелудь("ПроверяторВерсий");
ПроверяторВерсий.ЧтоНибудьПроверить();
В результате выполнения куска кода выше в переменную ПроверяторВерсий
прилетит свеженький блестящий желудь, добавленный ранее под именем "ПроверяторВерсий". Легко и просто, не правда ли?
Связывание компонентов между собой
Что нужно двум многоуважаемым Желудям для связи друг с другом? Правильно, пластилин. Каждый ребенок знает, что хорошая поделка - это желуди, пластилин и г... Мы немного отвлеклись.
Для указания зависимостей желудя служит аннотация &Пластилин
.
// file: Классы/ОбновляторВерсий.os
Перем _ПроверяторВерсий;
Перем _Логин;
Перем _Пароль;
&Желудь
Процедура ПриСозданииОбъекта(
&Пластилин ПроверяторВерсий,
&Пластилин Логин,
&Пластилин Пароль
)
_ПроверяторВерсий = ПроверяторВерсий;
_Логин = Логин;
_Пароль = Пароль;
КонецПроцедуры
Как же создать такой сложный желудь, сверху донизу обмазанный пластилином? Точно так же.
// file: main.os
ОбновляторВерсий = Поделка.НайтиЖелудь("ОбновляторВерсий");
ОбновляторВерсий.ОбновисьЧтоБыТамНеСтояло();
Заметьте, никаких зависимостей передавать не нужно. "ОСень" все взяла на себя - по именам параметров нашла зарегистрированные желуди и передала их в конструктор объекта.
Если вы начитались Овидия в оригинале или в вас вселился СОТОНА, а может просто потеряли совесть и захотели греческих букв в именах переменных, вы можете подсказать "ОСени", что за зависимость нужна в данном конкретном случае.
&Желудь
Процедура ПриСозданииОбъекта(
&Пластилин("ПроверяторВерсий") μ,
&Пластилин(Значение = "Логин") ξ,
&Пластилин("Пароль") ὦ
)
_ПроверяторВерсий = μ;
_Логин = ξ;
_Пароль = ὦ;
КонецПроцедуры
Имя нужного желудя передается в параметре "Значение" аннотации "&Пластилин". Если аннотация имеет один параметр или вы передаете только значение параметра "Значение", то имя параметра можно опустить.
Фабрика компонентов
Не все желуди обязаны являться полноценными классами с точки зрения системы типов 1Script. Согласитесь, странно заводить целый класс для хранения логина от ИТС, просто потому что кто-то пережарил желудей.
Философский вопрос в зал: откуда берутся желуди? Кто-нибудь? Может быть вы, в свитере цвета осенней листвы? Правильно, желуди растут на дубах! Дуб является источником желудей. На ветвях образуются цветочки, из цветочков появляются завязи, а из завязи - желуди.
И что самое приятное, дуб когда-то тоже был желудем, а значит, к нему применимы те правила игры, что и к обычным компонентам-желудям.
Итак, мы хотим передать Обновлятору логин и пароль в виде желудей. Для этого в новом классе, помеченном аннотацией &Дуб
, нужно объявить два метода, помеченные аннотацией &Завязь
. На дубе завязи, из завязей получатся желуди. Логично? Логично. Поехали!
// file: Классы/ДанныеАвторизации.os
&Дуб
Процедура ПриСозданииОбъекта()
КонецПроцедуры
&Завязь(Тип = "Строка")
Функция Логин() Экспорт
Возврат ПеременныеСреды().USERNAME;
КонецФункции
&Завязь(Тип = "Строка")
Функция Пароль(&Пластилин Логин) Экспорт
Если Логин = "user" Тогда
Возврат "password";
КонецЕсли;
Возврат ПеременныеСреды().PASSWORD;
КонецФункции
В листинге выше объявляются две функции-конструктора, возвращающие желуди. Как вы видите, желудь может быть чем угодно, а что угодно (в данном случае - строка) может быть желудем.
Т. к. Дуб
- это тоже желудь, а методы "Завязью" - это псевдо-конструкторы, то такой метод может быть скреплен пластилином с другими желудями. Плохие желуди могут даже хардкодить значения паролей, но мы закроем на это глаза.
В листинге выше &Завязь
содержит параметр Тип
. Он требуется, если из имени функции непонятно, что за тип она вернет. Ну, нам-то с вами понятно, что Пароль
- это строка, но вот глупой железке надо немного помочь.
С другой стороны, если вы объявляете желудь с "типовым" именем, то и параметр добавлять не нужно:
&Завязь
Функция Строка(&Пластилин Логин, &Пластилин Пароль) Экспорт
Возврат Логин + ":" + Пароль;
КонецФункции
Внедрение зависимостей в поля и функции-сеттеры
Не всегда бывает удобно пихать желуди прямо в конструктор. Например, желуди могут так сильно зависеть друг от друга, что образуют циклическую зависимость. Как Заяц-Волк, только Желудь-Желудь. В таком случае внедрить зависимости через конструктор не получится. На помощь нам придут внедрение желудей через экспортные поля и функции, принимающие значения.
// file: Классы/Обновлятор1С.os
&Пластилин
Перем Логин Экспорт; // Для успешного внедрения поле должно быть экспортным.
Перем Пароль;
&Пластилин
Процедура УстановитьПароль(Значение) Экспорт
Пароль = Значение;
КонецПроцедуры
&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры
При получении желудя Обновлятор1С
зависимость Логин
будет внедрена в поле напрямую, а зависимость Пароль
установлена через процедуру УстановитьПароль
.
Как и в случае с подстановкой желудей в конструктор, имя конкретного желудя может быть переопределено в параметре аннотации &Пластилин
. По умолчанию имя внедряемого желудя берется либо из имени поля ("Логин") либо из имени метода, из которого отбрасывается префикс "Установить": УстановитьПароль
->
Пароль
.
Внедрение настроек приложения
Хорошая поделка состоит не только из желудей и пластилина, но и обвешана маленькими приятными глазу детальками. Причем разные поделки, собранные по одному и тому же чертежу, могут только этими детальками и отличаться.
В примере выше мы передавали Обновлятору1С данные авторизации через переменные среды, читающиеся с помощью &Дуба
и его &Завязей
. Но это не единственный способ работы с настройками приложения.
Осень содержит в себе механизм передачи желудю настроек приложения напрямую из конфигурационного файла. Для этого служит аннотация &Деталька
. &Пластилин
помогает связать между собой несколько полноценных &Желудей
, тогда как &Деталька
налепляется сразу на нужный &Желудь
.
Поддержка загрузки настроек из переменных окружения и аргументов командной строки в ближайших планах разработки. Еще год-два и точно запилим, честно-честно!
Конфигурационный файл может быть в виде json
или yaml
файла, называется autumn-properties.json
/autumn-properties.yml
соответственно и ищется ОСенью в каталоге запуска приложения или в подкаталоге src
.
{
"Логин": "user"
"Пароль": "pass",
"ПрочиеНастройки": {
"Настроение": "Хорошее"
}
}
А вот так их можно использовать в жёлуде:
// file: Классы/Обновлятор1С.os
Перем _Логин;
Перем _Пароль;
Перем _Настроение;
Перем _Ответ;
&Желудь
Процедура ПриСозданииОбъекта(
&Деталька Логин,
&Деталька Пароль,
&Деталька("ПрочиеНастройки.Настроение") Настроение
&Деталька(Значение = "ПрочиеНастройки.ОтветНаГлавныйВопросЖизниВселеннойИВсегоОстального", ЗначениеПоУмолчанию = 42) Ответ
)
_Логин = Логин;
_Пароль = Пароль;
_Настроение = Настроение;
_Ответ = Ответ;
КонецПроцедуры
Вы можете добавлять к желудю любые детальки вне зависимости от того, есть они в конфигурационном файле или нет.
По умолчанию "ОСень" будет пытаться найти значение настройки по имени параметра конструктора: в json есть параметры "Логин" и "Пароль", в конструкторе есть параметры "Логин" и "Пароль", искра, буря, безумие!
JSON обычно содержит вложенные объекты и массивы. Путь к настройкам в таких вложенных структурах можно указать в параметре аннотации &Деталька
в формате библиотеки configor, используя точки для объектов и квадратные скобки для массивов.
Не обязательно заставлять пользователя указывать все настройки в конфигурационном файле, если ему могут подойти значения настроек по умолчанию. Для указания значения по умолчанию в &Детальке
есть параметр ЗначениеПоУмолчанию
. Поразительно, не правда ли?
Произвольные аргументы конструктора
Иногда понимание того, что же еще добавить в поделку, приходит в самый последний момент. Буквально после запуска приложения, кодом, на основании какой-то хитрой логики. Да, можно сделать хитрый &Дуб
, передать в него в виде желудя расчетчик этой самой бизнес-логики, который выдаст нужную циферку, но порой хочется просто передать 42
в конструктор желудя.
Для таких случаев в ОСени есть аннотация &Блестяшка
.
// file: Классы/ЛенивыйЖелудь.os
&Желудь
Процедура ПриСозданииОбъекта(
&Пластилин ПроверяторВерсий
&Блестяшка ФормулировкаВопроса
)
КонецПроцедуры
Как ее использовать? Двумя способами. Первый - это передать все параметры конструктора в виде массива в метод Поделка.НайтиЖелудь()
:
ПараметрыКонструктора = Новый Массив;
ПараметрыКонструктора.Добавить(Поделка.НайтиЖелудь("ПроверяторВерсий"));
ПараметрыКонструктора.Добавить("Главный вопрос жизни, вселенной и всего такого");
ЛенивыйЖелудь = Поделка.НайтиЖелудь(
"ЛенивыйЖелудь",
ПараметрыКонструктора
);
Второй - передать только блестяшки. А весь &Пластилин
и &Детальки
ОСень подставит сама:
ПараметрыКонструктора = Новый Массив;
ПараметрыКонструктора.Добавить("Главный вопрос жизни, вселенной и всего такого");
ЛенивыйЖелудь = Поделка.НайтиЖелудь(
"ЛенивыйЖелудь",
ПараметрыКонструктора
);
&Блестяшки
можно внедрять только в конструктор желудя. Внедрение в поля и методы жёлудя не поддерживается.
Алиасы компонентов
У желудей все как у людей. Есть свои увлечения, субкультуры и даже может быть &Прозвище
. Желудь Василий
в силу своего темного прошлого "у своих" зовется не иначе как Васян
. Как это отразить в ОСени?
// file: Классы/Василий.os
&Желудь
&Прозвище("Васян")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Как же разные группы желудей могут обращаться к нашему Василию? Сделать это можно и по имени и по прозвищу. И отзовётся при этом один и тот же желудь:
// file: main.os
Василий = Поделка.НайтиЖелудь("Василий");
Васян = Поделка.НайтиЖелудь("Васян");
Ожидаем.Что(Василий).Равен(Васян);
Василий может иметь много прозвищ. Поэтому аннотация &Прозвище повторяемая, т. е. на желудь ее можно навесить несколько раз с разными значениями и величать кого угодно и как угодно.
Группировка компонентов по алиасу
Представьте себе компанию панков. Каждый из них уникален, каждый - личность. Но при этом у них есть и что-то общее - все они панки. К каждому можно обратиться как: "Эй, ты, панк!", и он не только отзовется громогласным "Хой!", но возможно даже предложит Вам пива.
Попробуем описать нашу небольшую и дружную компанию:
// file: Классы/ДжонниРоттен.os
&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Классы/СтивДжонс.os
&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Классы/ПолКук.os
&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Классы/СидВишес.os
&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Чудесная компания собралась, неправда ли? Что же мы можем сделать с этими красавцами? Получить их как пачку желудей! В этом нам поможет метод Поделка.НайтиЖелуди()
:
Панки = Поделка.НайтиЖелуди("Панк");
Ожидаем.Что(Панки).ИмеетТип("Массив");
Ожидаем.Что(Панки.Количество()).Равно(4);
Для Каждого Панк Из Панки Цикл
Панк.Хой();
КонецЦикла;
Этот бэнд можно получить не только императивно, через НайтиЖелуди
, но и внедрить в качестве зависимости. Для указания того, что мы ждем массив из панков, а не какого-то конкретного, у аннотации &Пластилин
есть параметр Тип
, который может принимать значения "Желудь"
и "Массив"
:
// file: Классы/РокБэнд.os
Процедура ПриСозданииОбъекта(&Пластилин(Значение = "Панк", Тип = "Массив") Панки)
Для Каждого Панк Из Панки Цикл
Панк.Хой();
КонецЦикла;
КонецПроцедуры
Приоритизация компонентов с общим алиасом
Но давайте будем честны. Когда мы говорим про Sex Pistols, то в первую очередь вспоминаем Сида Вишеса, и только потом уже Джонни Роттена и остальных. А какой у него получился алко-my-way, ух! Аннотация &Верховный
подскажет "ОСени", кого вы считаете панком "по умолчанию":
// file: Классы/ДжонниРоттен.os
&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
---
// file: Классы/СидВишес.os
&Верховный
&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Без аннотации &Верховный
следующий код выдал бы ошибку, но с ней он прекрасно работает:
Панк = Поделка.НайтиЖелудь("Панк");
Ожидаем.Что(Панк).ИмеетТип("СидВишес");
Переопределение компонента по имени
Сила Верховного желудя настолько велика, что он может вытеснить данные о более слабом желуде из Поделки.
Вспомним любимые "типовые". Представьте, что есть некая типовая форма документа, которая нам почему-то не нравится. Например, хотим ее упростить для пользователя. Или как-нибудь ее покорежить. Можно влезть в код формы, но не всегда хочется так делать, а иногда и просто нельзя (привет, расширения и базовые конфигурации). На помощь нам может прийти копирование формы (с последующей модификацией) и/или подмена базовой формы собственной реализацией. Причем тут желуди?
От "типовой" нам пришла реализация некоего "важного компонента".
// file: Классы/ТиповойКомпонент.os
// важная, но все еще ненужная логика класса.
&Желудь("ВажныйКомпонент")
Процедура ПриСозданииНаСервере() Экспорт
КонецПроцедуры
Но она нам не нравится, и мы хотим подменить этот желудь собственной реализацией.
// file: Классы/НашСобственныйКомпонент.os
// важная, но все еще ненужная логика класса.
&Верховный
&Желудь("ВажныйКомпонент")
Процедура ПриСозданииНаСервере() Экспорт
КонецПроцедуры
Обратите внимание, что оба желудя описывают себя как "ВажныйКомпонент", то есть имеют одинаковое имя. В обычной ситуации ОСень выкинула бы исключение, т. к. не может быть двух желудей с одним именем.
Но использование аннотации &Верховный
подсказывает ОСени, что наша собственная реализация должна превалировать над типовой реализацией и позволит заместить желудь "ВажныйКомпонент" нашей версией.
Два &Верховных желудя с одинаковым именем ожидаемо выкинут исключение.
There can be only one!
Пост-инициализация компонента
Если вы все еще внимательно следите за нитью документации, у вас мог возникнуть вопрос вида "Что за херня тут происходит" "В каком порядке внедряются зависимости желудя?". И это очень хороший вопрос.
Установить значения в поля несозданного объекта или вызвать в нем какой-либо метод довольно проблематично. Поэтому:
- объект сначала создается (и вызывается его конструктор
ПриСозданииОбъекта
); - затем пластилином обмазываются поля класса;
- оставшиеся куски пластилина идут на внедрение зависимостей через вызов методов.
В такой ситуации может возникнуть желание что-нибудь поделать с желудем, когда в него уже всё-всё внедрено. И такая возможность есть! Создаем новый метод (на этот раз без пластилина) и указываем над им аннотацию &ФинальныйШтрих
.
// file: Классы/КлассСПостИнициализацией.os
&Пластилин
Перем Логин Экспорт;
&ФинальныйШтрих
Процедура Напоследочек() Экспорт
Сообщить("Логин здесь уже доступен: " + Логин);
КонецПроцедуры
&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Уникальность экземпляров компонентов
Желуди почти как люди. У каждого есть свой &Характер
. Кто-то показывает его явно, а кто-то ведет себя "как все".
Большинство желудей характеризуют себя как Одиночка
. Все желуди, которым понадобился ОбновляторВерсий
как зависимость, получат один и тот же экземпляр Обновлятора с одним и тем же (очень одиноким) состоянием. Ну, вы знаете. Одинокий одиночка, одиноко идущий по одинокой дороге. Один.
Однако не всегда это удобно. Предположим, вы написали свой супер-пупер уникальный генератор случайных чисел, который дает чудесное распределение. И хотите предоставить приложению возможность получать результат работы генератора в виде желудя, как зависимость. Будет не очень здорово, если все компоненты вашего приложения получат абсолютно случайно попавшееся число 42, не правда ли? Нам не подходят желуди-одиночки, нужно что-то более дружелюбное. Компанейское!
В решении этой проблемы нам поможет аннотация &Характер
.
// file: Классы/МойГенератор.os
Перем ГСЧ;
&Завязь
&Характер("Компанейский")
Функция СлучайноеЦелое() Экспорт
Возврат ГСЧ.СлучайноеЦелое();
КонецФункции
&Дуб
Процедура ПриСозданииОбъекта()
ГСЧ = Новый ГенераторСлучайныхЧисел();
КонецПроцедуры
Данный уникальный в своем роде генератор случайных чисел является "Дубом", то есть источником желудей. Его отличительной особенностью является его Компанейский
характер. Теперь любой желудь, который попросит себе зависимость СлучайноеЧисло
, действительно получит случайное число!
Дополнительная обработка компонента
Предположим, вы хотите сделать лошадку из желудей. Что для этого нужно? Для начала надо взять несколько желудей. Они будут немного отличаться друг от друга: тот, что покрупнее, пойдет на тело лошадки, тонкие желуди пойдут ноги, а вот этот смешной желудь в виде конуса будет мордой нашей лошадки. Конечно же, обмажем все пластилином, чтобы оно держалось вместе.
Вы смотрите на получившуюся лошадку и понимаете: что-то не то. Желуди-то все блестящие, полированные! А вы так мечтали о теплой и матовой лошадке. Что же делать? Есть решение: желуди нужно обработать напильником, чтобы придать им приятный матовый оттенок.
Конечно же вы можете добавить нужный код по приведению желудя к матовому цвету, например, в ПриСозданииОбъекта
. Но желуди-то разные, копипастить код между разными компонентами... Как-то фу. Хорошо, что "ОСень" может нам помочь.
Для дополнительной обработки объекта помимо "желудей" и "дубов" можно использовать &Напильник
. Это специальный объект с методом ОбработатьЖелудь
, который будет вызываться при каждом создании нового желудя.
// file: Классы/ПриданиеМатовогоЦветаЖелудям.os
Функция ОбработатьЖелудь(Желудь, ОпределениеЖелудя) Экспорт
ВжухнутьРазочек(Желудь);
Возврат Желудь;
КонецФункции
&Напильник(Порядок = 10)
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Метод обработки напильником возвращает желудь, причем не обязательно возвращать тот же самый желудь. Вам может захотеться обернуть его в объект-контейнер и накинуть на него несколько новых методов, например, с помощью decorator.
Напильник в этой удивительной осенней вселенной тоже является желудем, поэтому может иметь зависимости от других желудей. Но тут надо аккуратно - можно окончательно упороться и улететь таки на дно циклических зависимостей.
Чтобы уберечь себя ото дна, все напильники инициализируются перед запуском приложения. Как в жизни - сначала разложил рядом инструменты, а потом начинаешь творить.
"ОСень" в своем составе уже содержит два напильника: один отвечает за внедрение желудей в поля и методы установки значений, а второй - за вызовы методов &ФинальныйШтрих
.
Любой порядок стремится к хаосу, а несколько напильников - к порядку. Чтобы не запутаться, кто в какой последовательности вжухает по желудю у каждого Напильника
в соответствующей аннотации можно/нужно задать Порядок
исполнения. Напильник внедрения зависимостей имеет порядок 0
, а напильник "Финального штриха" - 999999
. Вы вольны занять любое значение порядка между этими границами. Не забудьте оставить немного места другим желающим!
Использование контекста приложения
Поделка
является входной точкой для работы вашего приложения. Через него вы собираете информацию о ваших желудях, инициализируете их. Приятной особенностью "поделки" является то, что он сам по себе тоже является желудем! Все есть желудь, помните же?
Поэтому вам никто не запретит с помощью Пластилина
прилепить Поделка
в ваш Желудь
, Дуб
или даже Напильник
. Главное - берегите свое ментальное здоровье.
Вынос логики регистрации желудей из точки запуска приложения
Иногда вам может потребоваться выбирать, какие именно желуди вы хотите добавлять в свою поделку. Или их просто много, и не хочется захламлять точку запуска приложения вызовами Поделка.ДобавитьЖелудь()
. Или по какой-то причине вам не подходит Поделка.ПросканироватьКаталог()
. На помощь придёт объект &Заготовка
, который можно расценивать как готовый набор желудей, добавляемый в поделку.
&Заготовка
- это очень хитрый &Желудь
! В отличие от обычных желудей он автоматически создается на фазе инициализации приложения, а затем у него вызывается метод ПриИнициализацииПоделки(Поделка)
. Желудёвость заготовки позволяет пользоваться частью возможностей обычных желудей - на нем будут срабатывать &Напильники
, однако из всего многообразия веществ зависимостей ему доступны только &Детальки
с настройками приложения. Ни тебе &Пластилина
, ни &Блестяшек
навесить на &Заготовку
нельзя.
// file: Классы/НаборЖелудей.os
Перем _ПереопределитьПроверятор;
&Заготовка
Процедура ПриСозданииОбъекта(&Деталька(ЗначениеПоУмолчанию = Ложь) ПереопределитьПроверятор)
_ПереопределитьПроверятор = ПереопределитьПроверятор;
КонецПроцедуры
Процедура ПриИнициализацииПоделки(Поделка) Экспорт
Поделка.ДобавитьЖелудь(Тип("Обновлятор1С"));
Поделка.ДобавитьЖелудь(Тип("ПроверяторВерсий");
Если _ПереопределитьПроверятор Тогда
Поделка.ДобавитьЖелудь("ВерховныйПроверяторВерсий");
КонецЕсли;
КонецПроцедуры
Использовать заготовку можно так:
// file: main.os
Поделка = Новый Поделка();
Поделка
.ДобавитьЗаготовку(Тип("НаборЖелудей"))
.ЗапуститьПриложение();
Лучше, чем дублировать ту же логику в main.os
, не правда ли?
Инверсия управления запуском приложения
Одной из конечных точкой использования фреймворка для внедрения зависимостей является отказ от какой-либо логики при запуске приложения. В конце концов мы же жёлуди в поделку добавляем не просто так, а чтобы они могли Сообщить("Привет, мир!")
.
Вместо императивного стиля в виде поиска желудя и вызова у него нужного метода, мы можем добавить в Поделку
... ещё один жёлудь с аннотацией &Рогатка
.
&Рогатка
- это специальный желудь, содержащий метод ПриЗапускеПриложения()
, который вызовется при, кхм, запуске приложения. Если быть точным, при вызове Поделка.ЗапуститьПриложение()
.
// file: main.os
Поделка = Новый Поделка();
Поделка
.ДобавитьРогатку(Тип("ПришедшийСМиром"))
.ЗапуститьПриложение();
// file: Классы/ПришедшийСМиром.os
&Деталька(ЗначениеПоУмолчанию = "Привет, Мир!")
Перем Приветствие Экспорт;
&Рогатка
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Процедура ПриЗапускеПриложения() Экспорт
Сообщить(Приветствие);
КонецПроцедуры
&Рогатка
является полноценным желудем, поэтому может принимать с свой конструктор и поля &Детальки
и &Пластилин
, а так же вжухается напильниками после создания.
Собственные аннотации
OneScript могуч. В отличие от материнской платформы, над любым методом может быть любое количество произвольных аннотаций, у них могут быть именованные и неименованные параметры. Аннотации даже могут быть над Перем
енными модуля и параметрами методов! Как тебе такое, УанЭс Энтерпрайз?
Но с большой силой приходит большая ответственность. Произвольные параметры произвольных аннотаций довольно сложно контролировать и единообразно обрабатывать. Поэтому ОСень с одной стороны немного загоняет Вас в рамки, а с другой - дает возможность проверять корректность использования аннотаций пользователем Вашего приложения или библиотеки. Даже если это Вы сами. Будем честными, больше всего со своим кодом косячим мы сами.
Помимо &Пластилина
, &Желудей
и прочих осенних продуктов, Вы можете описать собственную аннотацию. Для этого нужно создать новый класс, и навесить на него аннотацию &Аннотация
. Здорово, правда? (c)
// file: Классы/МояАннотация.os
&Аннотация("МояХитраяАннотация")
Процедура ПриСозданииОбъекта(Значение = "Трррунь", ОбязательныйПараметр)
КонецПроцедуры
По аналогии с &Желудем
, у &Аннотации
можно переопределить имя. Конструктор аннотации может принимать в себя любое количество именованных &Параметров
, в том числе "волшебный" параметр с именем Значение
. Так же для параметра можно указать значение по умолчанию.
Слышали про дог-фудинг?..
Каждая аннотация в ОСени представлена отдельным классом АннотацияИмя с навешенной
&Аннотацией
над конструктором.Да, в ОСени есть аннотация
&Аннотация
, описание которой расположено в классе с именемАннотацияАннотация.os
, над конструктором которого висит аннотация&Аннотация
соЗначение
=Аннотация
. Вот такая вот метафизика.
Наследование аннотаций
Представьте себе ситуацию, когда над несколькими компонентами системы нужно развесить несколько аннотаций по одному и тому же принципу. Например, объявить много компанейских &Желудей
с одинаковыми &Прозвищами
. Чтобы упростить себе жизнь, можно создать новую аннотацию, в которой скомбинировать несколько других. Например:
// file: Классы/МояАннотация.os
&Аннотация("КомпанейскийОбозванныйЖелудь")
&Характер("Компанейский")
&Желудь
&Прозвище("Обозванный")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Имея такую аннотацию, можно применить ее к любому компоненту:
// file: Классы/Компонент.os
&КомпанейскийОбозванныйЖелудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры
При инициализации контекста этот компонент будет автоматически помечен как компанейский
&Желудь
и с &Прозвищем
"Обозванный".
Можно добавить в нашу новую аннотацию универсальности, объявив в конструкторе новый &Параметр
и доопределив метод ПриРазворачиванииАннотации
:
// file: Классы/МояАннотация.os
Перем СохраненноеЗначение;
&Аннотация("КомпанейскийОбозванныйЖелудь")
&Характер("Компанейский")
&Желудь
&Прозвище("Заглушка")
Процедура ПриСозданииОбъекта(Значение)
СохраненноеЗначение = Значение;
КонецПроцедуры
Процедура ПриРазворачиванииАннотации(ОпределениеАннотации, ПодчиненныеАннотации, ТипВладельцаСвойств, Свойство) Экспорт
Аннотация = РаботаСАннотациями.НайтиАннотацию(
ПодчиненныеАннотации,
"Прозвище"
);
РаботаСАннотациями.УстановитьЗначениеПараметраАннотации(
Аннотация,
"Значение",
СохраненноеЗначение
);
КонецПроцедуры
// file: Классы/Компонент.os
&КомпанейскийОбозванныйЖелудь("Каштан")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
Пример кода выше хоть и может вызвать дикое желание записаться на курсы французского языка в Балашихе, сам по себе довольно прост. Значение нового &Параметр
, принятого в конструктор аннотации, сохраняется в переменную СохраненноеЗначение
.
При инициализации поделки происходит "разворачивание" всех имеющихся аннотаций как над свойствами так и над методами. Если в Вашей аннотации объявлен метод ПриРазворачиванииАннотации
, то он будет вызван с параметром ОпределениеАннотации
, содержащей информацию о текущей аннотации и всех подчиненных аннотациях первого уровня.
В примере выше среди подчиненных аннотаций ищется аннотация Прозвище
и ей устанавливается значение из переменной СохраненноеЗначение
. Таким образом для Компонент
будет установлено &Прозвище
"Каштан"
.
Обработка аннотаций
Если Вы уже попробовали делать свои &Напильники
, то скорее всего ознакомились с классом ОпределениеЖелудя
. Помимо хранения метаинформации о собственно желуде, методы Свойства()
и Методы()
возвращают информацию об аннотациях над свойствами и методами уже в "развернутом" и плоском виде.
Если же у Вас на руках только тип объекта, голый рефлектор и необходимость работы с аннотациями в обход "ОСени" (не надо так), то с помощью &Пластилина
можно прилепить к себе желудь РазворачивательАннотаций
, в котором можно найти методы РазвернутьАннотацииСвойств
и РазвернутьАннотацииСвойства
. И да, данные метода от рефлектора туда тоже можно передать.
Для всего остального есть модуль РаботаСАннотациями
.
Статический инициализатор контекста
В некоторых случаях может понадобиться указать "ОСени" набор заготовок до/без собственно создания новой Поделки
. Сейчас будет серьезный пример, т.к. на упоротых аналогиях объяснить такое не получается. И вообще, я птичка, мне такое сложно.
В составе "ОСени" есть метод Осень.ДобавитьЗаготовкуДляАвтоИнициализации(ИмяТипаЗаготовки)
, в который в качестве ИмяТипаЗаготовки
можно передать строку с именем типа. Добавленные таким образом &Заготовки
будут созданы и вызваны сразу же при создании Поделки
. Особенность этого метода в том, что его можно вызывать еще до полной прогрузки классов и модулей вашего приложения или импортируемой сторонней библиотеки.
Например, библиотека winow содержит модифицированный загрузчик библиотек, который прямо в момент #Использовать winow
добавит в контекст Поделки
информацию обо всех желудях, имеющихся в библиотеке, включая &Рогатку
, которая и запустит веб-сервер, реализованный в winow
.
Таким образом, точка входа в приложение может быть минималистичной настолько, насколько возможно. Например, полный текст модуля запуска приложения на winow
выглядит вот так:
#Использовать autumn
#Использовать winow
Поделка = Новый Поделка;
Поделка.ЗапуститьПриложение();
В коде выше сначала импортируется сама "ОСень", которая и предоставит модуль Осень
с методом добавления заготовок, а затем загрузится winow
, которая своим загрузчиком библиотеки наполнит "ОСень" заготовками. А уже при создании приложения и его запуске и произойдет вся магия регистрации и запуска желудей.
Такие дела.
Заключение
Если вы думаете, что мы упоролись, то вы в чем-то даже правы. Напоследок отмечу, что среди вариантов именования аннотаций "ОСени" еще была связка Гриб/Грибница/Спора/Рецепт для Желудь/Дуб/Завязь/Напильник соответственно. Так что еще не все потеряно. Надеюсь.
Спасибо, что дочитали. <3