Шаблон MVC для управляемого интерфейса







Мы воспринимаем как что-то само собой разумеющееся интуитивно понятный интерфейс, мгновенно реагирующий на наши клики, подстраивающийся под уже сделанный нами выбор. А между тем за этой возможностью — решение серьезных алгоритмических задач. В общем случае решения этих задач уже найдены, но проблема их конкретного применения остается как для выбранного окружения (веб-браузер, экран мобильного телефона, компьютер), так и возможностей языка программирования. В следующей статье представлено одно из таких применений общего решения на основе шаблона MVC для 1С в сочетании с возможностями управляемых форм и декларативного описания интерфейса.

Введение

Требования

Абстракция шаблона, программные слои

Объект (Model)

Форма (View)

Менеджер (Controller)

Зависимости

Описание связей

Виды зависимостей, слабые и сильные связи

Циклическая зависимость

Неявная зависимость

Заполнение значения

Значение по связям, обработчик заполнения

Заполнение значения из обработчика ПриИзменении

Подсистема «Управление интерфейсом»

Общее описание

Понятие реквизита объекта и узла. Реквизиты ссылочного и простого типа

Инициализация

Структура «ГрафЗависимостей»

События формы

НачалоВыбора

ПриИзменении

Изменение структуры Графа зависимостей

Сценарии работы

Определение договора

Определение номенклатуры по виду

Изменение подчинения договора на основного контрагента

Добавление зависимости вида номенклатуры от основного вида номенклатуры

Изменение договора (неявная слабая связь)

Изменение валюты (слабая связь)

Изменение контрагента программно

Изменение вида номенклатуры программно

Вывод

Приложение

Поставка

Развертывание

Демо версия

Рабочая

Источники


Введение

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

Программирование интерфейса пользователя — одна из самых сложных алгоритмических задач. История борьбы со сложностью в реализации пользовательского интерфейса уже достаточно продолжительная и придумано не мало решений для её обуздания. Найденные решения описаны в шаблонах проектирования. Остается взять «правильный» шаблон, проверить условия его применимости и найти его реализацию в 1С.

Базовым шаблоном в проектировании интерфейсов является шаблон MVC. В силу особенностей языка 1С найти реализацию этого шаблона было не просто, т.к. все эти шаблоны пришли из мира ООП, а 1С все-таки не поддерживает такую парадигму.

В представленном решении данной статьи описан вариант реализации MVC на 1С через декларативное описание зависимостей. Идея заключается в использовании графа зависимостей реквизитов, на основании которого подсистема сможет вызывать соответствующие обработчики. Сами обработчики можно расположить в общих модулях — это и будет средний слой между интерфейсом и моделью.

Рассмотрим реквизиты на форме. Некоторые из них имеют одну связь, а некоторые по несколько связей. Часть связей может зависеть от некоторого состояния объекта интерфейса и меняться: одни могут добавиться, другие удалиться. К примеру, выбор договора может быть привязан к контрагенту, а в случае, когда расчеты не прямые — к основному контрагенту.

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

Основные требования к решению:

  • Алгоритмы обработки изменения реквизитов должны располагаться так, чтобы для интерфейсного и неинтерфейсного вызова использовался бы один и тот же код
  • При изменении структуры зависимостей система должна рассчитать значения реквизитов, зависимости которых были изменены
  • Перед выбором значения реквизита система должна проверить полноту связей и выдать сообщение для незаполненных значений реквизитов параметров связей
  • Для пустого значения реквизита, заполняемого по умолчанию, система должна определить значение одним из способов:
    • заданным значением
    • значением, которое получается в результате вызова обработчика значения по умолчанию
    • по условиям параметров связей в случае найденного однозначного значения
  • Система должна проверять каждый зависимый реквизит на соответствие условиям параметров связей и в случае несоответствия сбрасывать значение в незаполненное состояние
  • Система должна вести трассировку путей расчета зависимостей. Система должна гарантировать прохождение уникальных путей.

Реализация взаимодействия формы и объекта модели хорошо описывается шаблоном MVC. Под MVC я имею в виду разделение реализации взаимодействия интерфейса и бизнес-логики на три составляющих: слой бизнес логики, слой посредника между интерфейсной частью и бизнес-логикой, слой интерфейса.

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

Таким образом, ключевая идея такой реализации: необходимо выделить слой посредника, который можно было бы использовать для взаимодействия с объектом как через форму, так и программно.

Расшифровка MVC в терминологии 1С у меня получилась следующей: M — Model — Объект, V — View — Форма, C — Controller — Менеджер. Получаем MVC = ОФМ (Объект, Форма, Менеджер).

Рисунок 1. Модель шаблона MVC

 

В объекте содержится вся функциональность, которая не связана с работой пользовательского интерфейса. В функциональность объекта входят: сохранение/восстановление, обработка данных, взаимодействие с БД, взаимодействие с другими объектами и внешними системами.

В случае 1С объект — это еще и экземпляр сущности со своим состоянием, соответствующий объекту метаданных 1С. Понимать это важно, так как способность сохранять свое состояние нам пригодится для реализации Менеджера.

Форма — это тоже объект, реализующий логику работы интерфейсных элементов. В нашей парадигме MVC объект-форма не должен реализовывать логику работы объекта модели. Его задача отслеживать изменения в объекте модели и отображать их конечный результат в интерфейсных элементах. Также в его задачу входит передавать изменения значений из интерфейсных элементов в значения реквизитов объекта модели.

Менеджер содержит код, который передает изменения из интерфейса в значения реквизитов объекта. Он также отвечает за логику связанных изменений в состоянии объекта. Это означает, что Менеджер поддерживает транзакционность изменений в состоянии объекта модели, инициируемых интерфейсом пользователя.

В роли Менеджера можно выбрать одноименный объект метаданных 1С — менеджер объекта или даже модуль самого объекта. В этом случае нарушается принцип независимости реализации Менеджера от Объекта и это ограничивает потенциальные возможности использования шаблона MVC. Лучшим вариантом будет реализация Менеджера в Общем модуле.

В конечном счете, я остановился на следующей реализации менеджера, весьма абстрактной. В качестве объекта менеджера в соответствии с парадигмой ООП у меня выступают данные и методы. Данные сохраняются либо в реквизите формы, либо в экспортной переменной объекта модели. В обоих случаях все состояние Менеджера содержится в одной структуре данных.

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

Для взаимодействия Формы с Менеджером необходимо в обработчиках событий реквизитов формы НачалоВыбора, ПриИзменении вставить вызовы методов Менеджера. Для первого обработчика Менеджер осуществляет проверку полноты связей, а для второго — выполняет обработчик ПриИзменении реквизита объекта, а также рассчитывает зависимости для выполнения цепочки обработчиков зависимых реквизитов.

Итак, обозначим круг задач, которые может решать Менеджер:

  • Накладывать ограничения на выбор реквизита
  • Определять значения по умолчанию
  • Определять полноту данных параметров связей реквизита
  • Выполнять обработку изменения реквизитов
  • Отслеживать зависимости реквизитов для реализации цепочек изменений зависимых реквизитов
  • Перестраивать зависимости и находить дельту для расчета реквизитов, по которым изменились зависимости
  • Взаимодействие Менеджера с Формой осуществляется по событиям формы:
    • НачалоВыбора
    • ПриИзменении

Ключевая концепция реализации шаблона MVC, предлагаемая мной, основана на описании зависимостей реквизитов объекта модели. При построении модели реализации шаблона я исходил из того, что каждый реквизит объекта модели может иметь свой обработчик ПриИзменении, а также может иметь зависимости от других реквизитов или параметров (аналогично как в метаданных можно описать СвязиПараметровВыбора и ПараметрыВыбора).

Зависимости я представил в виде графа. Полученный граф зависимостей содержит узлы и связи между ними. Каждый узел соответствует реквизиту объекта модели. Если реквизит принадлежит коллекции, то узел соответствует множеству реквизитов в соответствии с количеством строк коллекции.

Рисунок 2. Зависимости реквизитов формы, представленные в виде графа

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

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

Явное описание графа зависимостей позволяет Менеджеру как вычислять значения в узловых реквизитах, так и рассчитывать последовательности изменений. Таким образом, мы можем управлять последовательностями изменений декларативно, описывая зависимости в виде графа. Такой подход имеет следующие преимущества: конфигурацию зависимостей можно менять без изменения кода обработчиков, настройка зависимостей изначально имеет однозначное графическое представление и дает возможность визуального подтверждения требований у заказчика. К тому же использование графа позволяет задействовать законы из теории графов: контролировать возникновение циклов, оптимизировать расчет зависимостей для случаев, когда распространение изменений проходит через узловые реквизиты более одного раза. 

Выбор реквизита ограничивается значениями параметров его связей. Однако не все так однозначно. В классическом случае взаимозависимости рассмотрим пример: договор контрагента зависит от выбранной валюты и валюта определяется по выбранному договору. В рассмотренном примере неправильно ограничивать выбор договора установленным значением валюты или не давать перевыбрать валюту при выбранном договоре. Для такого случая используется понятие слабой связи — такая связь используется для расчета зависимостей и значений, однако не используется в качестве ограничивающей при выборе значения.

Наличие таких связей, которые не должны ограничивать выбор, но определяют значение, приводит к необходимости их как-то отличать. В предложенном варианте такие связи называются «слабыми» в отличии от связей «сильных». Первые определяют значение реквизита, но не ограничивают его выбор, тогда, как вторые не дают возможности выбрать значение, которое не удовлетворяет условиям связей.

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

Граф зависимостей может иметь циклы. Наличие циклов не всегда является ошибкой, однако без учета такой возможности программа «впадет» в бесконечный цикл.

Циклы могут порождаться как следствие сложной системы связей, так и наличием слабых связей.

Слабые связи двунаправленные и потому расчет зависимости в одну сторону порождает и обратный расчет.

Сложный граф зависимостей может приводить к появлению разных путей зависимостей от одного реквизита к другому. И это неотъемлемое свойство графа. Это обстоятельство приводит к появлению циклов и к повторению расчетов в узлах.

Решение — в отслеживании пройденных путей (связей реквизитов), а также бережный расчет зависимостей. В первом случае нужно не допускать повторения путей, во втором — проверять перед расчетом значения параметров связей на неизменность.

Не всегда получается выразить зависимость декларативным образом или это выражение очень сложно в реализации. Например, в рассмотренном ранее примере зависимости договора и валюты. Зависимость валюты от договора декларативно описывается как Отбор.Ссылка = ДоговорКонтрагента.ВалютаРасчетов, однако, на практике такую связь в параметрах связи указать нельзя, так как такая связь не поддерживается текущей платформой 1С.

Решений тут может быть несколько. Вот два из них:

  1. Можно описать зависимость как Отбор.Договор = ДоговорКонтрагента и программно обработать параметр Договор для несуществующего поля справочника Валюты.
  2. Реализовать определение значения валюты в обработчике при изменении реквизита ДоговорКонтрагента.

Первый вариант реализации я планирую подробно рассмотреть в отдельной статье.

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

Для заполнения значения реквизита можно определить обработчик. 

Заполнить значение можно и по связям. Если значение определяется однозначно, оно может быть заполнено.

Для реквизитов, которые не требуют обязательного заполнения, система не должна определять значение по умолчанию.

Значение может заполняться из обработчика ПриИзменении. Если в обработчике происходит заполнение значения зависимых реквизитов, то подсистеме необходимо сообщить об этом — так система сможет учесть изменения реквизитов для расчета зависимостей. Именно такой способ был предложен в качестве 2-го варианта для реализации связи ДоговорКонтрагента-Валюта.

Подсистема «Управление интерфейсом»

Общее описание

Рисунок 3. Диаграмма взаимодействия формы с подсистемой и менеджером

Перед началом работы подсистему нужно проинициализировать. В результате инициализации получается структура "Граф зависимостей". Эта структура хранится в реквизите формы или в экспортной переменной объекта.

Подсистема взаимодействует с объектом через его данные или вызовы обработчиков. Объект определяется из переданного в подсистему контекста. Если контекстом выступает форма, то объект доступен из основного реквизита формы. В случае когда взаимодействие с объектом осуществляется без формы, в качестве контекста используется сам объект (см. пример обработки Стенд с вызовом команд).

Точки вызова подсистемы определены для событий "Начало выбора" и "При изменении".

Инициализация

Инициализация подсистемы заключается в создании, заполнении и сохранении структуры "Граф зависимостей". Следующие функции используются для построения и сохранения структуры:

  • ПолучитьСтруктуруГрафаЗависимостей
  • ДобавитьУзел
  • ДобавитьСвязь
  • ДобавитьРеквизитФормыГрафЗависимостей
  • ЗаполнитьПараметрыВыбора

Листинг 1. Инициализация

//  Создание структуры
ГрафЗависимостей = РаботаСГрафомЗависимостейКлиентСервер.ПолучитьСтруктуруГрафаЗависимостей();
…
//  Добавление узлов
РаботаСГрафомЗависимостейКлиентСервер.ДобавитьУзел(ГрафЗависимостей, Префикс, "Контрагент", "Контрагент", , "Стенд.ПриИзмененииКонтрагента(Контекст)");
…
//  Добавление зависимостей
РаботаСГрафомЗависимостейКлиентСервер.ДобавитьСвязь(ГрафЗависимостей, Префикс, "Контрагент", "ДоговорКонтрагента" , "Отбор.Владелец");
…
//  Сохранение состояния графа зависимостей
РаботаСФормой.ДобавитьРеквизитФормыГрафЗависимостей(Контекст, ГрафЗависимостей);
РаботаСФормой.ЗаполнитьПараметрыВыбора(Контекст);

Структура «ГрафЗависимостей»

Данные подсистемы хранятся в структуре «Граф зависимостей». Эта структура создается в начале использования подсистемы и может модифицироваться в течение использования. Сама структура сохраняется либо в реквизите формы (реквизит создается динамически), либо в одноименной экспортной переменной модуля объекта (переменная должна быть задекларирована как экспортная).

Листинг 2. Сохранение состояния

//  Сохранение состояния графа зависимостей
Если ТипЗнч(Контекст) = Тип("УправляемаяФорма") Тогда
РаботаСФормой.ДобавитьРеквизитФормыГрафЗависимостей(Контекст, ГрафЗависимостей);
РаботаСФормой.ЗаполнитьПараметрыВыбора(Контекст);
Иначе
Контекст.ГрафЗависимостей = ГрафЗависимостей;
КонецЕсли;

Понятие реквизита объекта и узла. Реквизиты ссылочного типа или простого

Подсистема в процессе расчета зависимостей оперирует узлами и связями. В результате расчета зависимостей получаются реквизиты. Реквизиты могут быть ссылочного типа или простого. Для второго случая для расчета значения реквизита связи не используются.

Узел указывает на реквизит в метаданных. Для реквизитов коллекции используется описание узла без указания на конкретную строку. Под реквизитом понимается путь до данных объекта. Если реквизит является частью коллекции, то к описанию пути к данным добавляется указание идентификатора строки. В последнем случае идентификатором строки могут быть как идентификатор строки на форме, так и номер строки, если в качестве контекста выступает сам объект.

Реквизит может быть ссылочного типа или простого. Параметры связей используются для ограничения выбора значения реквизитов ссылочного типа. Для реквизитов простого типа вычисление значения реквизита по связям не используется.

Узлы используются для описания графа зависимостей, а реквизиты — для расчета зависимостей. 

События формы

Точками взаимодействия формы с подсистемой являются обработчики событий НачалоВыбора и ПриИзменении.

НачалоВыбора

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

Рисунок 4. Результат проверки полноты значений параметров связей для реквизита Договор контрагента.

ПриИзменении

После изменения значения реквизита производится вызов подсистемы для вызова обработчиков измененных и расчета зависимых реквизитов.

Рисунок 5.Изменённый граф зависимостей по отношению к Рисунку 2.

 

Рисунок 6. Переход в новое состояние графа зависимостей

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

Листинг 3. Изменение структуры зависимостей

Перем ГрафЗависимостей;//  Объявление в локальной переменной целевой структуры
//  Копирование в целевую структуру текущей структуры графа зависимостей для последующей модификации путем удаления/добавления связей
ГрафЗависимостей = ОбщегоНазначенияКлиентСервер.СкопироватьСтруктуру(ЭтаФорма.ГрафЗависимостей);
//  Удаление связи
РаботаСГрафомЗависимостейКлиентСервер.УдалитьСвязь(ГрафЗависимостей, Префикс, "ОсновнойКонтрагент", "ДоговорКонтрагента");
//  Добавление связи
РаботаСГрафомЗависимостейКлиентСервер.ДобавитьСвязь(ГрафЗависимостей, Префикс, "Контрагент", "ДоговорКонтрагента", "Отбор.Владелец");
//  Заполнение параметров связи формы и расчёт зависимых реквизитов
РаботаСФормой.ЗаполнитьПараметрыВыбора(ЭтаФорма, РаботаСГрафомЗависимостей.ПрименитьГрафЗависимостей(ЭтаФорма, ГрафЗависимостей));

Сценарии работы

Определение договора

Состояние: поле договор контрагента не заполнено, валюта заполнено Рубли.

  1. Пользователь: вызывает команду выбора контрагента из справочника, производит выбор
  2. Система: Вызывает обработчик Менеджера ПриИзменении для реквизита Контрагент
  3. Система: Определяет зависимые реквизиты: ДоговорКонтрагента
  4. Система: Производит поиск однозначного значения Договора контрагента по условиям связи
  5. Система: Заполняет значение договора найденным однозначным значением
  1. Пользователь: Вызывает команду Добавить строку ТЧ
  2. Пользователь: Заполняет значение Вида номенклатуры
  3. Система: Вызывает обработчик ПриИзменении реквизита ТЧ ВидНоменклатуры
  4. Система: Определяет зависимые реквизиты: Номенклатура
  5. Система: Производит поиск однозначного значения номенклатуры по условиям
  6. Система: Однозначное значение не найдено, значение реквизита Номенклатура остается прежним

Состояние: значения реквизитов «Контрагент», «Основной контрагент», «Договор контрагента», «Валюта» — заполнены, признак «Расчеты с основным контрагентом» сброшен.

  1. Пользователь: Устанавливает признак «Расчеты с основным контрагентом»
  2. Система: Изменяет условия параметров связей формы
  3. Система: Определяет измененные реквизиты: Основной контрагент (в реальности значение не изменено, но от него система начинает расчет, т.к. в структуре зависимостей появилась новая связь с этим реквизитом)
  4. Система: Определяет зависимые реквизиты: ДоговорКонтрагента
  5. Система: Производит поиск однозначного значения Договора основного контрагента по условиям связи
  6. Система: Заполняет значение договора найденным однозначным значением

Состояние: табличная часть заполнена различными значениями полей Вид номенклатуры, Номенклатура, признак «Общий вид номенклатуры» сброшен.

  1. Пользователь: Устанавливает признак «Общий вид номенклатуры»
  2. Система: Изменяет условия параметров связей формы
  3. Система: Определяет зависимые реквизиты: Вид номенклатуры [Номер строки], где Номер строки = 1..Кол-во строк
  4. Система: Выбирает 1-ую строку табличной части
  5. Система: Заполняет значение поля Вид номенклатуры значением реквизита шапки «Вид номенклатуры» (связь по значению ссылки)
  6. Система: Определяет зависимые реквизиты в 1-ой строке: Номенклатура[1]
  7. Система: Проверяет соответствие значения поля Номенклатура условиям связей
  8. Система: Значение поля не соответствует условиям связи – значение приводится к пустому
  9. Система: Производит поиск однозначного значения
  10. Система: Однозначное значение не найдено, значение остается незаполненным
  11. Переход к шагу 4 для расчета реквизита Вид номенклатуры в следующей строке табличной части

Состояние: заполнены значения реквизитов Контрагент, Валюта

  1. Пользователь: Выбирает договор в валюте, отличной от заполненного значения на форме
  2. Система: Выполняет обработчик ПриИзменении для реквизита Договор контрагента
  3. Система: В обработчике значение реквизита Валюта заполняется значением из договора, в переданную в обработчик структуру ИзмененныеРеквизиты добавляется реквизит «Валюта»
  4. Система: Фиксирует неявно пройденный путь Договор контрагента <-> Валюта
  5. Система: Определяет зависимые реквизиты от измененных реквизитов «Договор контрагента», «Валюта»: «Договор контрагента», «Банковский счет». Реквизит «Договор контрагента» будет пропущен, т.к. путь Валюта –> Договор контрагента считается пройденным (неявно). При этом дважды измененный реквизит системой рассчитывается — на основании того, что через него может быть несколько путей графа зависимостей.
  6. Система: Проверяет соответствие значения Банковский счет условиями связей
  7. Система: Очищает значение реквизита Банковский счет
  8. Система: Производит поиск однозначного значения
  9. Система: Заполняет значения реквизита Банковский счет найденным однозначным значением

Состояние: заполнены договор, валюта.

  1. Пользователь: Выбирает валюту, отличную от значения валюты договора
  2. Система: Определяет зависимые реквизиты: Договор контрагента, Банковский счет
  3. Система: Проверяет соответствие связям значений зависимых реквизитов
  4. Система: Очищает значения
  5. Система: Производит поиск однозначных значений
  6. Система: Заполняет значения по умолчанию

Состояние: заполнен реквизит «Валюта». Валюта нам нужна для поиска договора по умолчанию.

  1. Пользователь: Вызывает команду «При изменении контрагента»
  2. Система: Создает Объект модели
  3. Система: Инициирует подсистему «Расчет зависимостей реквизитов» через вызов процедуры Менеджера
  4. Система: Вызывает обработку события «При изменении реквизита» Контрагент
  5. Система: Вызывает обработчик реквизита Объекта «Контрагент»
  6. Система: Определяет зависимые реквизиты: Договор контрагента
  7. Система: Заполняет значение договора контрагента по умолчанию
  8. Система: Вызывает обработчик ПриИзменении для реквизита Договор контрагента
  9. Система: В обработчике значение реквизита Валюта заполняется значением из договора, в переданную в обработчик структуру ИзмененныеРеквизиты добавляется реквизит «Валюта»
  10. Система: Фиксирует неявно пройденный путь Договор контрагента <-> Валюта
  11. Система: Определяет зависимые реквизиты от измененных реквизитов «Договор контрагента», «Валюта»: «Договор контрагента», «Банковский счет». Реквизит «Договор контрагента» будет пропущен, т.к. путь Валюта –> Договор контрагента считается пройденным (неявно). При этом дважды измененный реквизит системой рассчитывается — на основании того, что через него может быть несколько путей графа зависимостей.
  12. Система: Проверяет соответствие значения Банковский счет условиями связей
  13. Система: Очищает значение реквизита Банковский счет
  14. Система: Производит поиск однозначного значения
  15. Система: Заполняет значения реквизита Банковский счет найденным однозначным значением

Листинг 4. Команда «При изменении контрагента»

ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
Стенд.ИнициализацияГрафаЗависимостей(ОбработкаОбъект);
РаботаСГрафомЗависимостей.ПриИзмененииЗначенияРеквизита(ОбработкаОбъект, "Контрагент");
ЗначениеВРеквизитФормы(ОбработкаОбъект, "Объект");

Состояние: в табличной части заполнена 1-ая строка.

  1. Пользователь: Вызывает команду «При изменении вида номенклатуры»
  2. Система: Создает Объект модели
  3. Система: Инициирует подсистему «Расчет зависимостей реквизитов» через вызов процедуры Менеджера
  4. Система: Заполняет значение Вида номенклатуры «Мебель»
  5. Система: Вызывает обработку события при изменении реквизита Вид номенклатуры для 1-ой строки
  6. Система: Определяет зависимые реквизиты: Номенклатура[строка ведущего реквизита]. В данном случае ведущий и зависимые реквизиты принадлежат одной таблице, а, значит, зависимость распространяется по строке измененного реквизита
  7. Система: Проверяет значение поля Номенклатура условиям связей
  8. Система: Очищает значение поля Номенклатура

Исключения:

7.б.          Значение поля Номенклатура удовлетворяет условиям связей (это возможно, если предыдущее значение вида номенклатура было таким же)

7.б.1.      Система: Значение поля Номенклатура остается неизменным

Листинг 5. Команда «При изменении вида номенклатуры»

ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
Если ОбработкаОбъект.Товары.Количество() = 0 Тогда
Возврат;
КонецЕсли;
Стенд.ИнициализацияГрафаЗависимостей(ОбработкаОбъект);
Мебель = Справочники._ДемоВидыНоменклатуры.НайтиПоНаименованию("Мебель", Истина);
ТекущаяСтрока = 0;
ОбработкаОбъект.Товары[ТекущаяСтрока].ВидНоменклатуры = Мебель;
РаботаСГрафомЗависимостей.ПриИзмененииЗначенияРеквизита(ОбработкаОбъект, "Товары.ВидНоменклатуры", ТекущаяСтрока);
ЗначениеВРеквизитФормы(ОбработкаОбъект, "Объект");

 

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

Следование шаблону MVC также позволяет переиспользовать алгоритмы обработчиков как в различных формах отображения объекта модели, так и при программном управлении объектом модели.

Более того, получившаяся модель позволяет выделять устойчивые связи реквизитов и переиспользовать их описания для различных объектов модели. Так, например, для разных видов документов можно выделить такие устойчивые связи: Контрагент-Договор-Валюта, Сумма документа-Валюта документа-Сумма расчетов-Валюта расчетов. Обработчики для таких связей можно сделать общими и это может значительно повысить качество системы: устранить дублирование кода для обслуживания однотипных зависимостей.

Решение поставляется в виде конфигурации с общим демо модулем Стенд и одноименной обработкой. Подсистема использует общие процедуры БСП.

Таблица 1 Состав поставки

Метаданные

Наименование

Назначение

ОбщийМодуль

ОбщийКлиентСервер

Вспомогательные процедуры

 

РаботаСГрафомЗависимостей

Подсистема расчета зависимостей реквизитов

 

РаботаСГрафомЗависимостейКлиентСервер

Подсистема расчета зависимостей реквизитов

 

РаботаСоСхемойЗапроса

Вспомогательные процедуры

 

РаботаСФормой

Подсистема расчета зависимостей реквизитов формы

 

РаботаСФормойКлиентСервер

Подсистема расчета зависимостей реквизитов формы

 

Стенд

Менеджер в общем модуле

Обработка

Стенд

Обработка для тестов

  1. Установить Демо конфигурацию БСП (проверялось на 2.4)
  2. Объединить с конфигурацией поставки УправляемыйИнтерфейсДемо.cf: добавить общие модули подсистемы и модуль Стенд для демо-теста (не затрите модуль приложения и настройки основной конфигурации!).
  3. Установить расширение Стенд.cfe (требуется версия платформы 8.3.11)

Рабочая

  1. Объединить с конфигурацией поставки по подсистеме «УправлениеИнтерфейсом»
  1. Сандерс У., Кумаранатунг Ч. "ActionScript 3.0. Шаблоны проектирования". Глава 12. Шаблон Модель-Представление-Контроллер
  2. Фаулер, Мартин. Архитектура корпоративных программных приложений. Глава 14. Типовые решения, предназначенные для представления в Web
  3. Эрик Фримен, Элизабет Фримен, Кэтти Сьерра, Берт Бейтс — Паттерны проектирования. Глава 12. MVC как комбинация паттернов

 

37 Comments

  1. nomadon

    тема конечно…

    Reply
  2. ruizave

    Чем-то больше похоже не на MVC, а на реактивное программирование.

    Reply
  3. ArchLord42

    Вспоминается альфа авто с ее «обработказначениеРеквизита» или как там уже не помню. Знатный батхерд периодами ловил когда дебажил эту процедуру.

    Reply
  4. kalyaka

    (2) Действительно похоже, вот выдержка из wiki: “К примеру, в MVC архитектуре с помощью реактивного программирования можно реализовать автоматическое отражение изменений из Model в View и наоборот из View в Model”

    Reply
  5. kalyaka

    (3) Собираюсь внедрять эту подсистему в боевой системе, так что первые баги беру на себя 🙂

    Reply
  6. ruizave

    (5)А вы потери по производительности замеряли на постоянное обращение в контроллер? В случае, например, сложной таблицы, где от изменения реквизита формы необходимо пересчитать по цепочке значения ячеек таблицы.

    По скорости разработки добавление новых зависимостей быстрее чем работа с оповещениями?

    Пробовали вынести граф зависимостей в справочник или регистр конфигурации, что бы без обновления была возможность динамически изменять поведение?

    Reply
  7. Gureev

    Интересно.. Но смысл? Какую это несет бизнес-выгоду?

    Reply
  8. Сурикат

    А вы можете привести примеры для чего вам понадобилось столь сильное усложнение?

    И какую выгоду и кому это принесет. При первом прочтении упрощения какого-то по сравнению со стандартным подходом не заметил =(

    Reply
  9. kalyaka

    (7) Я проводил замеры производительности на своей рабочей базе на предыдущей версии реализации подсистемы и не выявил критичного проседания.

    А так, конечно, требуется и тесты написать и производительность померить — все это должно выявить ограничения и точки проработки.

    Я планирую внедрить в подсистему замер производительности APDEX и тогда любой сможет посмотреть производительность на своем железе сам.

    Про работу с оповещениями не думал. У меня все связи хранятся в одной структуре и работа происходит в рамках одной формы, так что как использовать оповещения я пока не представляю.

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

    Reply
  10. kalyaka

    (8) Бизнес-выгода зависит от бизнеса 🙂

    Я исхожу из того, что декларативное описание поведения системы позволяет улучшить качество за счет упрощения: системе нужно рассказать Что сделать, а система должна решить — Как, и сделать это наилучшим образом.

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

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

    Reply
  11. kalyaka

    (9) Пара примеров есть в демо базе.

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

    Reply
  12. kalyaka

    (8) (12) Также исхожу из того, что использование подсистемы должно позволить реализовывать более сложное поведение интерфейса с меньшими затратами при высоком качестве.

    Reply
  13. Сурикат

    (12)

    Просто на мой взгляд MVC прежде всего нужен для синхронизации модели и представления. А 1С об этом позаботилось за нас

    Ну и управлением получением данных

    Reply
  14. sigmov

    Идея хорошая в плане привязки к себе клиента.

    Вряд ли кто то кроме Вас сможет потом это сопровождать.

    Так что клиент уже никуды не денется. )))

    Сам программирую на в т.ч. и на C# и на python и вот там MVC это MVC, а вот MVC в 1С это как попытка натянуть шапку на другую часть тела, только потому что там тоже есть голова )))

    В 1С для таких шаблонов не хватает парадигмы javascript о том что любая вещь — объект и функция и любая сущность.

    А в 1С объектность функций заменили «жалкими» описаниеоповещения и на этом все ((((

    Reply
  15. kalyaka

    И какой выход? Написать свою «тонну кода» и тогда клиент будет рад?

    Если Вы программируете на других языках, то разве Вы не используете чужие фреймворки и библиотеки, выложенные на ресурсах типа Гитхаба?

    Есть такой закон — «Закон сохранения сложности», который говорит, что как бы мы не старались, сложность никуда не денется 🙂 Однако она может быть перемещена или «загнана» в черный ящик и тогда у Вас есть шанс сделать что-то еще более сложное 🙂

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

    Reply
  16. vasvl123

    самоуправляемые формы)

    Reply
  17. kalyaka

    (17) Лучше управляемые на основе декларативного описания или расширение декларативного описания поведения управляемых форм. Языки программирования двигаются в сторону функционального уклона, а это есть суть — декларативное программирование. Фишка в том, чтобы системе можно было указать Что нужно, а система должны выбрать Как и сделать это наилучшим способом.

    Reply
  18. Maxisussr

    (0)

    Интересно.

    Еще бы хорошо представить сравнительную таблицу, на примере разработки внешней обработки подбора чего-нить.

    Сколько заняла разработка «до», сколько «после».

    Сколько заняло изменение логики «до», сколько «после» (до-после это «классический» подход и описанный подход MVC).

    Тогда все вопросы про выгоду бизнесу отпадут.

    Reply
  19. sigmov

    (16) Вывод — не усложнять.

    > Если Вы программируете на других языках, то разве Вы не используете чужие фреймворки и библиотеки, выложенные на ресурсах типа Гитхаба?

    Использую, но далеко не все.

    Собственно претензий к вашему фреймверку и задумке нет. Просто архитектура 1С очень недружественна к такому фреймверку => будет очень сложно его сопровождать и каждый раз «натягивать» этот фреймверк на объекты. ИМХО, это съест всю потенциальную выгоду.

    Reply
  20. kalyaka

    (19) Я планирую опубликовать еще несколько статей по практическому применению описанного решения.

    Сейчас я работаю над переводом рабочей конфигурации на использование этой подсистемы и по результату у меня будет уже достаточно практического материала — вот тогда и отчитаюсь 🙂

    Reply
  21. kalyaka

    (9)

    “При первом прочтении упрощения какого-то по сравнению со стандартным подходом не заметил =(“

    Поясните, пожалуйста, что Вы понимаете под стандартным подходом?

    Стандартный подход, я так понимаю, — это просто писать код обработчиков событий реквизитов. Эти обработчики должны менять состояние объекта. И здесь возникает диллема: где разместить код по изменению связанных реквизитов? Можно раскидать по обработчикам событий этих реквизитов, а можно объединить в единой универсальной процедуре для обработки всех реквизитов и вызывать ее рекурсивно и много других способов.

    Следующая проблема, которую нужно будет решить, — как избежать дублирования кода, если у объекта есть несколько форм ввода? Наверное, нужно будет тогда вынести обработчики в некий общий модуль. Далее, нужно решить, как эти же обработчики можно было бы использовать для объекта вообще без формы, из программного кода?

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

    Далее Вы можете им поделиться с сообществом и тогда уже другие разработчики не будут изобретать велосипед, а смогут решать более интересные бизнес-задачи.

    (14)

    В общем, то, что я описал выше, это и составляет суть шаблона MVC. А на счет того, что 1С прекрасно справляется с синхронизацией модели и представления — тут я с Вами полностью согласен.

    Reply
  22. WalterMort

    Толстые, тупые, уродливые контроллеры пробрались и в 1С (даже в википедии в статье про MVC в разделе «Наиболее частые ошибки» это написано), к каноничному MVC это не имеет никакого отношения.

    Второе, что навевает статья — анекдот про «только бухгалтерии прибавилось».

    Reply
  23. kalyaka

    (23)

    MVC в разделе «Наиболее частые ошибки»

    Предложенное решение никак Вас не ограничивает в выборе реализации контроллера: это может быть контроллер с бизнес-логикой или её элементами, а может быть и вызов методов самого объекта (в примере см. вызов команд). Во втором случае в качестве контроллера будет выступать предложенная подсистема с настроенным графом зависимостей — это как раз вариант «тонкого» контроллера, который только передает вызовы в модель.

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

    Итак, есть варианты:

    1. Подсистема+модуль инициализации зависимостей — это контроллер, который передает вызовы в модель (требуется преобразование данных объекта в объект)

    2. Подсистема+Менеджер объекта или общий модуль — это контроллер, который работает с данными объекта и фактически реализует бизнес-модель. Объект тоже использует реализацию бизнес-логики из менеджера объекта или общего модуля, однако обратное не поддерживается.

    2-ой вариант — это ТТУК, однако у него есть как недостатки, так и преимущества.

    Недостатки: реализация бизнес-логики разбросана за рамками объекта, потенциал объекта не задействован.

    Преимущества: части бизнес-логики можно переиспользовать в других объектах, часть бизнес-логики можно реализовать без требования серверного контекста.

    В ООП парадигме я бы придерживался 1-го варианта и перечисленное преимущество 2-го для 1С решал бы в рамках ООП в системе с поддержкой ООП. В 1С я более склонен ко 2-ому варианту и вот почему. Часть бизнес-логики имеет предопределенные точки вызова (обработка проведения, заполнения и т.д.) и не пересекается с логикой работы интерфейса или интерактивного изменения состояния объекта и объект в этом плане остается объектом своего типа (документ, справочник). Реализацию же остальной бизнес-логики вполне можно разместить в менеджере объекта (почти как в родительском классе объекта в мире ООП эта реализация становится доступной для всех объектов данного вида). Общую бизнес-логику в общем модуле (почти как в родительском классе более высокого уровня абстракции — для разного типа объектов).

    Reply
  24. kote

    Тоже делал.. но давно было

    https://infostart.ru/public/74872/

    Reply
  25. 7OH

    (15) согласен на все 100 — сначала в 1С должно появиться ООП.

    Reply
  26. NCCSOFT

    (16) Не использовать 1С для этого, чтобы получить максимальный эффект, т.к. ядро «чёрный ящик» (связи там не изменить нам).

    Пишу своё ядро «с нуля», БД на PostgreSQL разворачивается автоматически. Автоформы тоже будут. Про логику пока не буду говорить.

    По повожу сложности. Система — совокупность взаимосвязанных элементов. У элементов свойства. У системы — общие свойства. Внутри системы есть подсистемы, и это тоже системы. Так вот — фишка в том, что как раз сложность выражена в том, что этих подсистем много. А если их объединить в систему, то за счёт (двойного и более порядков) сокращения внешних связей и внешних свойств — общая система станет значительно проще. Не знаю, понятно ли написал. Т.е. получается, что части больше, чем целое. Фраза «Банан большой, а кожура еще больше» — для меня не является смешной 🙂

    Поэтому, вот так, я и пытаюсь сократить эту сложность.

    Reply
  27. kalyaka

    (27) Рекомендую ознакомиться с законом сохранения сложности 🙂

    http://rsdn.org/article/philosophy/Complexity.xml

    Reply
  28. zzumma

    Зачем всё это?

    Reply
  29. NCCSOFT

    (28) посмотрел, это классификация «сложностей» субъекта 🙂 я имел в виду «сложности» объекта как системы. Объект разумеется предприятие

    Reply
  30. user774630

    (29) какой ответ ждет человек с мышлением

    «Однозначно, семёрка гораздо проще, а большинству предприятий большего и не надо.»

    ?

    Reply
  31. zzumma

    (31) ?

    Reply
  32. leemuar

    (8) в общем — уменьшение стоимости последующих изменений, future costs. Очень большая проблема в том, что адекватно это измерить и объяснить бизнесу — очень сложно. Подробнее об этом у Фаулера и дальше по ссылкам: https://martinfowler.com/bliki/TechnicalDebt.html

    Reply
  33. NoRazum

    Скачал, посмотрел демо.

    Работа большая проделана.

    Остается вопрос с производительностью.

    Любой клик идет через вызов сервера.

    То что в контексте вызов идет, так сама форма в параметрах передается.

    После внедрения есть результаты?

    Reply
  34. kalyaka

    (34) В рабочем варианте подсистема на мой взгляд оправдала ожидания.

    В обновленном варианте подсистема сама создает необходимые обработчики в форме, за счет чего в модуле формы код практически исчез. Вся функциональность формы перешла в модуль менеджера объекта.

    Пока реализовал только на одном объекте, следующим шагом будет перевод других объектов (это документы) с выделением общего поведения в общий модуль.

    По производительности. Для документов с количеством строк не более 10 визуального проседания реакции формы не заметно. В следующей версии подсистемы я планирую подключить APDEX, тогда данные будут более объективные.

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

    Про контекст формы. Подсистема ориентирована на расчет реквизитов, при этом для нее нет разницы каких: формы, объекта формы, данных формы, — поэтому контекст необходим. По своему опыту и опыту коллег могу сказать, что в последних версиях платформы 1С сильно преуспела в эффективности передачи изменений контекста формы. Так, если есть изменение формы, то рано или поздно оно должно прийти на сервер, обратно не всегда. Однако если нет изменений, то и лишней передачи данных контекста тоже не будет.

    Reply
  35. NoRazum

    (35) как добавите APDEX выкладывайте, буду ждать. Чтоб в свои конфигурации попробовать.

    Reply
  36. leonid.kovalenko

    А почему именно MVC? Ведь есть и другие альтернативы: EBI, DCI, MVP, MVVM, PAC, RMR, MOVE и ADR. Некоторые из них, даже, заточены под клиент-серверную архитектуру.

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

    Reply
  37. kalyaka

    (37)

    А почему именно MVC?

    MVC — это базовый шаблон для идеи разделения реализации. А вот конкретизация как реализовать этот шаблон приводит к различным вариантам, для которых и придуманы все эти многочисленные аббревиатуры.

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

    Это все равно как если в вопросе реализации контроллера для нашего случае рассматривать вариант: в модуле объекта, в общем модуле, в модуле менеджера — и для каждого варианта придумать свою аббревиатуру.

    PS: в скором времени планирую обновить статью. В статье будет представлена практическая реализация шаблона, реально проверенная в действующей системе.

    Следите за обновлениями 🙂

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *