Skip to content

autumn-library/autumn

Repository files navigation

Autumn/ОСень

Тестирование Статус порога качества

Что такое осень - это желудь!

Осень…. прекрасная пора. Ею вдохновлялся Пушкин, говорил, что осенью его особенно прёт. Именно это и произошло с авторами фреймворка "ОСень". Потому что осень - это прекрасно.
Когда приложение становится большим, вам потребуется дерево (желательно дуб) и немного пластилина. Ах, да, обязательно творческое осеннее настроение, чай и стабильная психика.

Фреймворк компонентных приложений для 1Script под названием "ОСень" поможет вам невероятным магическим образом компоновать ваше приложение из компонентов, которым не нужно заниматься собственным созданием или настройкой. И все это будет щедро обмазано пластилином и приятно пахнуть дубовыми вениками.

Оглавление

Зачем мне это?

Вот есть у вас объект, у которого объявлен конструктор с параметрами. И есть много мест, где он создается. Параметры этого конструктора называются зависимостями. То есть, объект не может жить без передачи ему этих параметров, он от них зависит.
А где взять значения этих параметров, чтобы передать в объект? Очевидно что их тоже надо создать (через Новый или откуда-то получить). А у них тоже есть зависимости, и у зависимостей зависимостей есть зависимости.

Зависимость. Да, вот слово, которое приходит в голову, когда изучаешь API "ОСени". Зависимость у авторов явно есть. Но это неправда. Мы зависимы только от просмотра зоопорно красивого кода и вообще, не одобряем нехороших веществ, разве что пластилин (не тот) и желуди. Даа, желуди нам определенно нравятся, ведь они овальные и в смешных шапочках.

Но! шутки в сторону. В ваших руках не просто пластилин и желуди, в ваших руках - Dependency Injection Framework для любимого языка программирования. Теперь ваши объекты будут опрятными и шелковистыми создаваться сами, и не нужно будет думать как добыть параметры конструирования, сколько их, в каком порядке они идут. Достаточно сказать: "Хочу класс УгольныйКомбайн" - а марка колес, двигатель и прочие запчасти КАК-ТО создадутся и передадутся в конструктор.

Теперь приложение может состоять из сложных компонентов, которые намного проще менять и перенастраивать. Вот был у вас класс ОбновляторВерсии1С. В конструкторе получал логин/пароль пользователя. И все было хорошо, но понадобилось вам в этот класс добавить знание об уже установленных версиях 1С, чтобы не скачивать лишние с сайта. Можно прямо в этом классе написать проверку установленных версий, но это нарушение ПЕО (Принцип Единой Ответственности): проверятор версий не надо смешивать с обновлятором. Мало ли в каких еще местах пригодится проверятор версий, а мы его жестко внутрь другого класса зашьем… Повторное использование - наше все.

Чтобы всё было по красоте, нам надо передать в конструктор объект ПроверяторВерсий, который предоставит Обновлятору1С информацию о том, что за версии у нас уже установлены. И все бы ничего, но Обновлятор1С создается через Новый в тысяче мест. В эти места нужно залезть, и дополнительно там создать ПроверяторВерсий и передать его в конструктор... А если Проверятор тоже имеет зависимость (а кто ее не имеет в наше сложное время, а?), тогда придется протащить всё дерево зависимостей через все методы и компоненты чуть ли не от самого старта приложения. Так жить нельзя, такая жесткая связность будет мешать развитию приложения и усложнять его.

Осенью, когда вам хочется создать Обновлятор1С, вы просто говорите "Лунная призма, дай мне силы!" Осень.НайтиЖелудь("Обновлятор1С") - и все зависимости зависимостей создадутся сами. Сколько у них параметров в конструкторе, как они создаются, кто им передает параметры и где берет - это знает только "ОСень". К чему вдаваться в метафизику… "ОСень" возьмет ваши проблемы на себя, главное - показать ей, где лежит пластилин...

Как с этим работать?

Инициализация приложения

"ОСень" - это вам non penis canina est, а фреймворк. Любой порядочный фреймворк нужно немножечко сконфигурировать, чтобы дальше все заработало.

Для инициализации контекста "ОСени" служит класс Поделка, который необходимо создать через Новый (один разочек можно и написать это вредное слово), а затем наполнить Желудями, Дубами и Напильниками. Нет, мы не упоролись, скоро расскажем, что тут к чему.

Инициализировать контекст можно так:

// file: main.os

#Использовать autumn

Поделка = Новый Поделка();

И... всё.

При создании Поделки ОСень автоматически просканирует все доступные в системе типов классы, определит, кто их них желудь, а то дуб, и последовательно добавит их в контекст.

Вероятно, помимо загрузки собственно ОСени, вы захотите так же зарегистрировать и собственные классы. Единственное, что для этого нужно, это выполнить обычное для OneScript подключение библиотек по имени или пути:

// 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 или ini файла, называется autumn-properties.json/autumn-properties.yml/autumn-properties.ini соответственно и ищется ОСенью в каталоге запуска приложения или в подкаталоге src.

{
    "Логин": "user",
    "Пароль": "pass",
    "ПрочиеНастройки": {
        "Настроение": "Хорошее"
    }
}

А вот так их можно использовать в жёлуде:

// file: Классы/Обновлятор1С.os

&Деталька
Перем Логин;

&Деталька
Перем Пароль;

&Деталька("ПрочиеНастройки.Настроение")
Перем Настроение;

&Деталька(Значение = "ПрочиеНастройки.ОтветНаГлавныйВопросЖизниВселеннойИВсегоОстального", ЗначениеПоУмолчанию = 42)
Перем Ответ;

&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры

Вы можете добавлять к желудю любые детальки вне зависимости от того, есть они в конфигурационном файле или нет.

По умолчанию "ОСень" будет пытаться найти значение настройки по имени параметра конструктора: в json есть параметры "Логин" и "Пароль", в конструкторе есть параметры "Логин" и "Пароль", искра, буря, безумие!

JSON обычно содержит вложенные объекты и массивы. Путь к настройкам в таких вложенных структурах можно указать в параметре аннотации &Деталька в формате библиотеки configor, используя точки для объектов и квадратные скобки для массивов.

Не обязательно заставлять пользователя указывать все настройки в конфигурационном файле, если ему могут подойти значения настроек по умолчанию. Для указания значения по умолчанию в &Детальке есть параметр ЗначениеПоУмолчанию. Поразительно, не правда ли?

Произвольные аргументы конструктора

Иногда понимание того, что же еще добавить в поделку, приходит в самый последний момент. Буквально после запуска приложения, кодом, на основании какой-то хитрой логики. Да, можно сделать хитрый &Дуб, передать в него в виде желудя расчетчик этой самой бизнес-логики, который выдаст нужную циферку, но порой хочется просто передать 42 в конструктор желудя.

Для таких случаев в ОСени есть аннотация &Блестяшка.

// file: Классы/ЛенивыйЖелудь.os

&Желудь
Процедура ПриСозданииОбъекта(
    &Пластилин ПроверяторВерсий
    &Блестяшка ФормулировкаВопроса
)
КонецПроцедуры

Как ее использовать? Двумя способами. Первый - это передать все параметры конструктора в виде массива в метод Поделка.НайтиЖелудь():

ПараметрыКонструктора = Новый Массив;
ПараметрыКонструктора.Добавить(Поделка.НайтиЖелудь("ПроверяторВерсий"));
ПараметрыКонструктора.Добавить("Главный вопрос жизни, вселенной и всего такого");

ЛенивыйЖелудь = Поделка.НайтиЖелудь(
    "ЛенивыйЖелудь",
    ПараметрыКонструктора
);

Второй - передать только блестяшки. А весь &Пластилин и &Детальки ОСень подставит сама:

ПараметрыКонструктора = Новый Массив;
ПараметрыКонструктора.Добавить("Главный вопрос жизни, вселенной и всего такого");

ЛенивыйЖелудь = Поделка.НайтиЖелудь(
    "ЛенивыйЖелудь",
    ПараметрыКонструктора
);

&Блестяшки можно внедрять только в конструктор желудя. Внедрение в поля и методы жёлудя не поддерживается. Однако вы можете объявить &Дуб и &Завязь, куда &Блестяшку передать таки можно:

// file: Классы/ДубСЛенивымЖелудем.os

&Дуб
Процедура ПриСозданииОбъекта()
КонецПроцедуры

&Завязь
Процедура ЛенивыйЖелудь(
    &Пластилин ПроверяторВерсий
    &Блестяшка ФормулировкаВопроса
)
    ЛенивыйЖелудь = Новый ЛенивыйЖелудь(ПроверяторВерсий);
    ЛенивыйЖелудь.ЗадатьВопрос(ФормулировкаВопроса);

    Возврат ЛенивыйЖелудь;
КонецПроцедуры

И в заключение темы, ЛенивыйЖелудь (не важно, созданный конструктором или &Завязью) можно внедрить в другой желудь, воспользовавшись повторяемым параметром Блестяшка в аннотации &Пластилин:

// file: Классы/ЛюбительЛенивыхЖелудей.os

&Пластилин(Блестяшка = "Шестью девять?")
Перем ЛенивыйЖелудь;

&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры

При поиске любителя ленивых желудей в него будет прилеплен ЛенивыйЖелудь с вопросом Шестью девять?.

Повторяемые параметры аннотаций позволяют указывать один и тот же параметр аннотации несколько раз с разными значениями. Например, если у желудя в конструктор принимается несколько блестяшек, то и внедрить такой желудь можно указав несколько блестяшек в аннотации &Пластилин:

&Пластилин(Блестяшка = "Первое значение", Блестяшка = "Второе значение")
Перем ЖелудьСДвумяБлестяшками;

Генератор экземпляров компонентов

Иногда возникает необходимость получить желудь-зависимость отложено. Например, по какому-то условию или с передачей произвольных параметров. Эту задачу можно решить через внедрение в желудь Поделки целиком, но умные книжки учат нас, что зависеть от глобального контекста приложения - это вообще-то фу-фу-фу и не надо так. Для уменьшения связывания полезного кода с инфраструктурным в ОСени есть дополнительная аннотация &Табакерка. Если ее навесить над полем или параметром метода, над которым уже висит &Пластилин, &Деталька или &Блестяшка, то вместо внедрения в поле или параметр непосредственно нужного значения будет внедрен специальный объект типа Табакерка, который позволяет достать желудь/детальку/блестяшку по требованию.

// file: Классы/КлассСТабакеркой.os

&Табакерка
&Пластилин
Перем ПроверяторВерсий;

Функция ЧтоНибудьОтложеноПроверить(Версия) Экспорт
    РеальныйПроверяторВерсий = ПроверяторВерсий.Достать();
    
    Возврат РеальныйПроверяторВерсий.Проверить(Версия);
КонецФункции

&Желудь
Процедура ПриСозданииОбъекта()
КонецПроцедуры

В примере выше в поле ПроверяторВерсий будет внедрен объект типа Табакерка, который позволит получить желудь ПроверяторВерсий по требованию.

В метод Табакерка.Достать() можно передать параметр-массив с прилепляемыми частицами, аналогично тому, как это делается для Поделка.НайтиЖелудь(ИмяЖелудя, МассивПрилепляемыхЧастиц). Таким образом, можно получить желудь с прилепленными к нему блестяшками или другими рассчитываемыми желудями.

Алиасы компонентов

У желудей все как у людей. Есть свои увлечения, субкультуры и даже может быть &Прозвище. Желудь Василий в силу своего темного прошлого "у своих" зовется не иначе как Васян. Как это отразить в ОСени?

// file: Классы/Василий.os

&Желудь
&Прозвище("Васян")
Процедура ПриСозданииОбъекта()
КонецПроцедуры

Как же разные группы желудей могут обращаться к нашему Василию? Сделать это можно и по имени и по прозвищу. И отзовётся при этом один и тот же желудь:

// file: main.os

Василий = Поделка.НайтиЖелудь("Василий");
Васян = Поделка.НайтиЖелудь("Васян");

Ожидаем.Что(Василий).Равен(Васян);

Василий может иметь много прозвищ. Поэтому аннотация &Прозвище повторяемая, т. е. на желудь ее можно навесить несколько раз с разными значениями и величать кого угодно и как угодно.

Группировка компонентов по алиасу

Представьте себе компанию панков. Каждый из них уникален, каждый - личность. Но при этом у них есть и что-то общее - все они панки. К каждому можно обратиться как: "Эй, ты, панк!", и он не только отзовется громогласным "Хой!", но возможно даже предложит Вам пива.

Попробуем описать нашу небольшую и дружную компанию:

// file: Классы/ДжонниРоттен.os

&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Классы/СтивДжонс.os

&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Классы/ПолКук.os

&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры
// file: Классы/СидВишес.os

&Желудь
&Прозвище("Панк")
Процедура ПриСозданииОбъекта()
КонецПроцедуры

Чудесная компания собралась, неправда ли? Что же мы можем сделать с этими красавцами? Получить их как пачку желудей! В этом нам поможет метод Поделка.НайтиЖелуди():

Панки = Поделка.НайтиЖелуди("Панк");

Ожидаем.Что(Панки).ИмеетТип("Массив");
Ожидаем.Что(Панки.Количество()).Равно(4);

Для Каждого Панк Из Панки Цикл
    Панк.Хой();
КонецЦикла;

Этот бэнд можно получить не только императивно, через НайтиЖелуди, но и внедрить в качестве зависимости. Для указания того, что мы ждем массив из панков, а не какого-то конкретного, у аннотации &Пластилин есть параметр Тип, который может принимать значения всех &ПрилепляемаяКоллекция, в частности Массив:

// file: Классы/РокБэнд.os

Процедура ПриСозданииОбъекта(&Пластилин(Значение = "Панк", Тип = "Массив") Панки)
    Для Каждого Панк Из Панки Цикл
        Панк.Хой();
    КонецЦикла;
КонецПроцедуры

Аналогичный параметр (третий) есть и у метода НайтиЖелуди:

Панки = Поделка.НайтиЖелуди("Панк", , "Массив");

По умолчанию, через &Пластилин внедряется единичный Желудь, а метод НайтиЖелуди возвращает Массив.

Помимо единичного желудя и массива, РокБэнд можно получить в виде соответствия ("Соответствие"), где ключом будет имя панка жёлудя, а значением - сам жёлудь, и в виде таблицы значений ("ТаблицаЗначений"), в каждой строке которой будет содержаться Имя, ОпределениеЖелудя и сам Желудь.

И вообще, загляните в документацию библиотеки autumn-collections, там много вкусных типов!

И даже больше, РокБэнд можно получить своим собственным способом, для этого нужно создать у себя свой уникальный СобирательПанков.os, в котором нужно определить два метода, один который получает на входе панка и его описание, а второй который возвращает собранную банду

// СобирательПанков.os

Перем Банда;

// Тут нам на вход приходит очередной панк
Процедура Добавить(Панк, ОпределениеПанка) Экспорт

    Для Каждого РольИИсполнитель Из Банда Цикл

        Если Не ЗначениеЗаполнено(РольИИсполнитель.Значение) Тогда

            Банда.Вставить(РольИИсполнитель.Ключ, Желудь);
            Прервать;

        КонецЕсли;

    КонецЦикла;

	Коллекция.Добавить(ОпределениеЖелудя.Имя());

КонецПроцедуры

// А тут нам надо вернуть собранную банду
Функция Получить() Экспорт
	Возврат Новый ФиксированнаяСтруктура(Банда);
КонецФункции

&ПрилепляемаяКоллекция("СобирательПанков")
Процедура ПриСозданииОбъекта()

    Банда = Новый Структура(
        "Гитарист, Барабанщик, БасГитарист, Вокалист"
    );

КонецПроцедуры

Теперь нам вернётся целая собранная группа:

Панки = Поделка.НайтиЖелуди("Панк", , "СобирательПанков");

// > Панки.Гитарист
// > Панки.Барабанщик
// > Панки.БасГитарист
// > Панки.Вокалист

Упорядочивание компонентов при получении в виде сортированных коллекций

Если вы прилепляете себе несколько желудей с общим прозвищем в виде массива или таблицы значений, вам может понадобиться расставить их по некоторому порядку. Не будем вдаваться в подробности, кто в SexPistols третий панк, а кто второй, но точно можно предположить, что сессионные музыканты должны быть в самом конце массива панков. В этом нам поможет аннотация &Порядок, принимающая числовое значение порядка.

// file: Классы/СессионныйМузыкант.os

&Желудь
&Прозвище("Панк")
&Порядок(5)
Процедура ПриСозданииОбъекта()
КонецПроцедуры

По умолчанию, каждый желудь имеет порядок, равный единице.

&Рогатка и &Напильник тоже могут быть упорядочены!

Приоритизация компонентов с общим алиасом

Но давайте будем честны. Когда мы говорим про 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.

Не каждый напильник стоит применять ко всем желудям. Вспоминая замечательный пример с панками, мы можем захотеть добавить напильник для полировки заклепок на напульсниках наших музыкантов. При этом очевидно, что далеко не все носят напульсники, да еще и с заклепками. В задаче ограничения применения напильника поможет повторяемый параметр аннотации &Напильник под названием ПрименяетсяТолькоНа. В нем можно указать имена или прозвища желудей, к которым применяется данный напильник.

// file: Классы/ПолировщикЗаклепок.os

&Напильник(ПрименяетсяТолькоНа = "Панк")
Процедура ПриСозданииОбъекта()

КонецПроцедуры

С другой стороны сам желудь может захотеть ограничить список напильников, которые могут по нему вжухнуть. Или даже вовсе отключить вжухание всех напильников. Для этого на желудь можно навесить аннотацию &ОсобоеОбращение, которая может принимать булев параметр ОтключитьВсеНапильники, повторяемый строковый параметр ПрименятьТолькоНапильник и опять же повторяемый строковый параметр НеПрименятьНапильник. Назначение первого параметра, надеемся, достаточно очевидно, поэтому вот пример работы с одним из других:

// file: Классы/ПозерВНапульсниках.os

&Желудь
&ОсобоеОбращение(
	ПрименятьТолькоНапильник = "ПолировщикЗаклепок"
)
Процедура ПриСозданииОбъекта()
КонецПроцедуры

Зачем все это может быть нужно? Перфоманса ради, например. В случае большого количества компанейских желудей, которые не нуждаются в обработке напильником, можно отключить работу всех напильников для них. Ведь зачем тратить время на обработку, если она не нужна? При этом можно сохранить все плюшки от внедрения зависимостей и деталек через конструктор. Удобненько.

Напильник в этой удивительной осенней вселенной тоже является желудем, поэтому может иметь зависимости от других желудей. Но тут надо аккуратно - можно окончательно упороться и улететь таки на дно циклических зависимостей.

Чтобы уберечь себя ото дна, все напильники инициализируются перед запуском приложения. Как в жизни - сначала разложил рядом инструменты, а потом начинаешь творить.

"ОСень" в своем составе уже содержит два напильника: один отвечает за внедрение желудей в поля и методы установки значений, а второй - за вызовы методов &ФинальныйШтрих.

Любой порядок стремится к хаосу, а несколько напильников - к порядку. Чтобы не запутаться, кто в какой последовательности вжухает по желудю каждому Напильнику в аннотации &Порядок можно/нужно задать порядок исполнения. Напильник внедрения зависимостей имеет порядок 0, а напильник "Финального штриха" - 999999. Вы вольны занять любое значение порядка между этими границами.

Использование контекста приложения

Поделка является входной точкой для работы вашего приложения. Через него вы собираете информацию о ваших желудях, инициализируете их. Приятной особенностью "поделки" является то, что он сам по себе тоже является желудем! Все есть желудь, помните же?

Поэтому вам никто не запретит с помощью Пластилина прилепить Поделка в ваш Желудь, Дуб или даже Напильник. Главное - берегите свое ментальное здоровье.

Условная регистрация компонентов в контексте приложения

Иногда вам может потребоваться выбирать, какие именно классы вы хотите добавлять в свою поделку. Например, вы хотите загружать определенный класс только при выполнении какого-нибудь условия (из-за конфликта имен, к примеру). На помощь придёт объект &Заготовка, который можно расценивать как готовый набор желудей, добавляемый в поделку.

&Заготовка - это очень хитрый &Желудь! В отличие от обычных желудей он автоматически создается на фазе инициализации приложения, а затем у него вызывается метод ПриИнициализацииПоделки(Поделка). Желудёвость заготовки позволяет пользоваться частью возможностей обычных желудей - на нем могут срабатывать &Напильники, добавленные в Поделку до &Заготовки, однако из всего многообразия веществ зависимостей ему доступны только &Детальки с настройками приложения. Ни тебе &Пластилина, ни &Блестяшек навесить на &Заготовку нельзя.

// file: Классы/НаборЖелудей.os

Перем _ПереопределитьПроверятор;

&Заготовка
Процедура ПриСозданииОбъекта(&Деталька(ЗначениеПоУмолчанию = Ложь) ПереопределитьПроверятор)
    _ПереопределитьПроверятор = ПереопределитьПроверятор;
КонецПроцедуры

Процедура ПриИнициализацииПоделки(Поделка) Экспорт
   
    ПутьКСценарию = "./ОбычныйПроверятор.os";

    Если _ПереопределитьПроверятор Тогда
        ПутьКСценарию = "./НеобычныйПроверятор.os";
    КонецЕсли;

    ПодключитьСценарий(ПутьКСценарию, "Проверятор");

КонецПроцедуры

Инверсия управления запуском приложения

Одной из конечных точкой использования фреймворка для внедрения зависимостей является отказ от какой-либо логики при запуске приложения. В конце концов мы же жёлуди в поделку добавляем не просто так, а чтобы они могли Сообщить("Привет, мир!").

Вместо императивного стиля в виде поиска желудя и вызова у него нужного метода, мы можем добавить в Поделку... ещё один жёлудь с аннотацией &Рогатка.

&Рогатка - это специальный желудь, содержащий метод ПриЗапускеПриложения(), который вызовется при, кхм, запуске приложения. Если еще точнее, при вызове Поделка.ЗапуститьПриложение().

// file: Классы/ПришедшийСМиром.os

&Деталька(ЗначениеПоУмолчанию = "Привет, Мир!")
Перем Приветствие;

&Рогатка
Процедура ПриСозданииОбъекта()
КонецПроцедуры

Процедура ПриЗапускеПриложения() Экспорт
    Сообщить(Приветствие);
КонецПроцедуры

&Рогатка является полноценным желудем, поэтому может принимать с свой конструктор и поля &Детальки и &Пластилин, а так же вжухается напильниками после создания.

По умолчанию, все &Рогатки запускаются в фоновом задании (да, в OneScript есть фоновые задания!), затем ОСень начинает ожидать завершения работы каждой &Рогатки и выводить сообщения об ошибке, если вдруг выстрел не удался. Это поведение можно переопределить с помощью параметров аннотации с говорящими названиями ЗапускатьВФоне и ОжидатьЗавершения.

// file: Классы/СинхронноПришедшийСМиром.os

&Рогатка(ЗапускатьВФоне = Ложь)
Процедура ПриСозданииОбъекта()
КонецПроцедуры

Процедура ПриЗапускеПриложения() Экспорт
    Сообщить("Привет, Мир!");
КонецПроцедуры

Следует аккуратно писать код в &Рогатках, запускаемых не в фоновом режиме, т.к. в этом случае Поделка будет ожидать завершения такой не-фоновой &Рогатки и не запускать остальные &Рогатки. Если же ПриЗапускеПриложения() в такой &Рогатке выбросит исключение, то запуск приложения остановится, а работа приложения завершится.

Как и любой &Желудь, &Рогатка может иметь аннотацию &Порядок. Она будет влиять на порядок запуска рогаток, что особенно важно для рогаток, запускающихся не в фоне.

Собственные аннотации

OneScript могуч. В отличие от материнской платформы, над любым методом может быть любое количество произвольных аннотаций, у них могут быть именованные и неименованные параметры. Аннотации даже могут быть над Переменными модуля и параметрами методов! Как тебе такое, УанЭс Энтерпрайз?

Но с большой силой приходит большая ответственность. Произвольные параметры произвольных аннотаций довольно сложно контролировать и единообразно обрабатывать. Поэтому ОСень с одной стороны немного загоняет Вас в рамки, а с другой - дает возможность проверять корректность использования аннотаций пользователем Вашего приложения или библиотеки. Даже если это Вы сами. Будем честными, больше всего со своим кодом косячим мы сами.

Помимо &Пластилина, &Желудей и прочих осенних продуктов, Вы можете описать собственную аннотацию. Для этого нужно создать новый класс, и навесить на него аннотацию &Аннотация. Здорово, правда? (c)

// file: Классы/МояАннотация.os

&Аннотация("МояХитраяАннотация")
Процедура ПриСозданииОбъекта(Значение = "Трррунь", ОбязательныйПараметр)

КонецПроцедуры

По аналогии с &Желудем, у &Аннотации можно переопределить имя. Конструктор аннотации может принимать в себя любое количество именованных &Параметров, в том числе "волшебный" параметр с именем Значение. Так же для параметра можно указать значение по умолчанию.

Слышали про дог-фудинг?..

Каждая аннотация в ОСени представлена отдельным классом АннотацияИмя с навешенной &Аннотацией над конструктором.

Да, в ОСени есть аннотация &Аннотация, описание которой расположено в классе с именем АннотацияАннотация.os, над конструктором которого висит аннотация &Аннотация со Значение = Аннотация. Вот такая вот метафизика.

Если вы хотите разрешить пользователю ваших аннотаций указывать несколько значений для одного и того же параметра, его нужно пометить аннотацией &Повторяемый. Например, вот так выглядит описание аннотации &Пластилин:

&Аннотация("Пластилин")
Процедура ПриСозданииОбъекта(Значение = "", Тип = "", &Повторяемый Блестяшка = Неопределено)

КонецПроцедуры

Наследование аннотаций

Представьте себе ситуацию, когда над несколькими компонентами системы нужно развесить несколько аннотаций по одному и тому же принципу. Например, объявить много компанейских &Желудей с одинаковыми &Прозвищами. Чтобы упростить себе жизнь, можно создать новую аннотацию, в которой скомбинировать несколько других. Например:

// file: Классы/МояАннотация.os

&Аннотация("КомпанейскийОбозванныйЖелудь")
&Характер("Компанейский")
&Желудь
&Прозвище("Обозванный")
Процедура ПриСозданииОбъекта()

КонецПроцедуры

Имея такую аннотацию, можно применить ее к любому компоненту:

// file: Классы/Компонент.os

&КомпанейскийОбозванныйЖелудь
Процедура ПриСозданииОбъекта()

КонецПроцедуры

При инициализации контекста этот компонент будет автоматически помечен как компанейский &Желудь и с &Прозвищем "Обозванный".

Можно добавить в нашу новую аннотацию универсальности, объявив в конструкторе новый &Параметр и доопределив метод ПриРазворачиванииАннотации:

// file: Классы/МояАннотация.os

Перем СохраненноеЗначение;

&Аннотация("КомпанейскийОбозванныйЖелудь")
&Характер("Компанейский")
&Желудь
&Прозвище("Заглушка")
Процедура ПриСозданииОбъекта(Значение)
    СохраненноеЗначение = Значение;
КонецПроцедуры

Процедура ПриРазворачиванииАннотации(ОпределениеАннотации, ПодчиненныеАннотации, ТипВладельцаСвойств, Свойство) Экспорт
    Аннотация = РаботаСАннотациями.НайтиАннотацию(
        ПодчиненныеАннотации,
        "Прозвище"
    );

    РаботаСАннотациями.УстановитьЗначениеПараметраАннотации(
        Аннотация,
        "Значение",
        СохраненноеЗначение
    );
КонецПроцедуры
// file: Классы/Компонент.os

&КомпанейскийОбозванныйЖелудь("Каштан")
Процедура ПриСозданииОбъекта()

КонецПроцедуры

Пример кода выше хоть и может вызвать дикое желание записаться на курсы французского языка в Балашихе, сам по себе довольно прост. Значение нового &Параметр, принятого в конструктор аннотации, сохраняется в переменную СохраненноеЗначение.

При инициализации поделки происходит "разворачивание" всех имеющихся аннотаций как над свойствами так и над методами. Если в Вашей аннотации объявлен метод ПриРазворачиванииАннотации, то он будет вызван с параметром ОпределениеАннотации, содержащей информацию о текущей аннотации и всех подчиненных аннотациях первого уровня.

В примере выше среди подчиненных аннотаций ищется аннотация Прозвище и ей устанавливается значение из переменной СохраненноеЗначение. Таким образом для Компонент будет установлено &Прозвище "Каштан".

Обработка аннотаций

Если Вы уже попробовали делать свои &Напильники, то скорее всего ознакомились с классом ОпределениеЖелудя. Помимо хранения метаинформации о собственно желуде, методы Свойства() и Методы() возвращают информацию об аннотациях над свойствами и методами уже в "развернутом" и плоском виде.

Если же у Вас на руках только тип объекта, голый рефлектор и необходимость работы с аннотациями в обход "ОСени" (не надо так), то с помощью &Пластилина можно прилепить к себе желудь РазворачивательАннотаций, в котором можно найти методы РазвернутьАннотацииСвойств и РазвернутьАннотацииСвойства. И да, данные метода от рефлектора туда тоже можно передать.

Для всего остального есть модуль РаботаСАннотациями.

В предыдущих сериях

В прошлых версиях библиотеки и/или документации вы могли встречать описание таких вещи как:

  • методы Поделка.ДобавитьЖелудь()/Поделка.ДобавитьДуб() и другие методы добавления желудей в поделку;
  • метод Поделка.ПросканироватьКаталог();
  • модуль Осень и автоматическую регистрацию заготовок для запуска при создании поделки;
  • модуль ТипыПрилепляемыхЖелудей;

Все это устарело и теперь либо не используется, либо просто не нужно. Да, следить за чейнджлогами полезно!

Заключение

Если вы думаете, что мы упоролись, то вы в чем-то даже правы. Напоследок отмечу, что среди вариантов именования аннотаций "ОСени" еще была связка Гриб/Грибница/Спора/Рецепт для Желудь/Дуб/Завязь/Напильник соответственно. Так что еще не все потеряно. Надеюсь.

Спасибо, что дочитали. <3