Введение
Программирование интерфейса - одна из ключевых задач при разработке средств автоматизации. В очередной раз, берясь за разработку интерфейса документа, я решил обобщить свой опыт и написать статью на тему: программирование интерфейсов на 1С.
С какими трудностями можно столкнуться при разработке интерфейса? Одно дело - Вы разработчик системы с нуля, и другое - чего у меня случалось чаще - Вы дорабатываете интерфейс типовой конфигурации, в которой от типового функционала осталось только название. И вот я в очередной раз смотрю на творение n-го количества программистов, поработавших в коде до меня: ни тебе системного подхода, ни качества кода, ни оформления (как этот код работает - никто не знает). А как это работает? На поверку оказывается, что, в общем и целом - работает. Однако при невыясненных обстоятельствах иногда становится недоступным нужный реквизит, или тип реквизита не тот, или вообще часть реквизитов не видна. А еще, почему то, при простом открытии формы на экран, в документе изменяются значения!?
И вот теперь этот документ нужно доработать. Всего лишь добавить один реквизитик. Как только Вы это сделали - все, теперь проклятие документа падет на Вас! А как Вы хотели? С документом раньше работали и все было - ОК, а тут после Вашей доработки вдруг полезли косяки... Особенно интересно, когда такие претензии выдвигают уже Вам, хотя Вы всего лишь добавили один реквизит.
Вообщем у меня есть один подход. Я его выработал и успешно применяю уже много лет. В этой статье я хочу поделиться своим опытом, и может быть этот опыт поможет Вам лучше осознать свою деятельность и не быть одним из тех n-ых программеров, которые просто добавляют реквизиты и требуют деньги за свою работу (я не против, любая работа должна быть оплачена, но не любая работа достойна достойной оплаты). Возможно, я здесь задел достаточно острый вопрос качества кода, ресурсов, бюджета и т.д. Но не буду отвлекаться.
MVC
Немного теории на тему. Существует весьма известный шаблон проектирования интерфейса - MVC (Model View Control) 1)2). Шаблон состоит из трех компонентов: модель данных (Model), пользовательский интерфейс (View), управляющая логика (Control). Реализация каждого из компонентов делается отдельно, а их сочетание позволяет пользователю работать с данными через интерфейс. Такая модель реализации позволяет не только разделять работу по проектированию собственно интерфейса (View), управляющей логики (Control) и функциональной логики приложения (Model), но и создавать различные сочетания этих трех компонентов.
Фактически в 1С можно реализовать независимо два компонента модели: Document-View и Model 3).
Рисунок 1. Иллюстрация компонентов модели MVC. Источник: http://www.dossier-andreas.net/software_architecture/mvc.html
Реализация идеи MVC на 1С
Пример реализации
Перейду к конкретным примерам. Возьмем объект метаданных «Документ». Так в роли Document-View будем считать реализацию пользовательского интерфейса в форме документа. В реализации пользовательского интерфейса 1С для документа могут выступать: форма списка, форма выбора, форма документа, специальная форма ввода и т.д. Вся управляющая логика может быть представлена процедурами модуля формы документа. Функциональная логика (Model) реализована в модуле документа.
Суть метода MVC в реализации на 1С
Использование подхода MVC для 1С заключается в разделении реализации формы (далее интерфейс я буду по 1С-овски просто называть формой) и объекта. Все события формы должны вызывать соответствующие процедуры объекта, изменяющие данные. Вся обработка данных должна быть в объекте (это так называемая бизнес-логика). При этом изменение одних данных может влиять на другие и наоборот - одни данные могут зависеть от других. Такой подход позволяет использовать одни и те же процедуры обработки данных объекта как из кода (из бизнес-логики), так и из интерфейса (из различных форм). Понимаете о чем я?
Маленькая иллюстрация. Как то мне нужно было написать для документа процедуру заполнения на основании. Документ был не типовой и реализовывался несколькими программистами. Процедуры обработки данных были реализованы полностью в форме. Для реализации алгоритма заполнения в модуле документа мне пришлось вычленить процедуры обработки данных из формы и перенести их в модуль. После проделанной работы по отделению процедур обработки данных документа (бизнес-логики) и процедур обработки интерфейсных событий мне оставалось просто использовать те же процедуры, как из формы, так из моего кода заполнения документа на основании.
Далее все зависимости данных объекта я представляю в виде диаграммы зависимостей. Устанавливаю последовательность распространения изменений и начинаю программировать. При этом процедуры работы с данными (бизнес-логики) располагаются в модуле документа, а вызовы процедур реализованы из процедур событий формы. Для реализации распространения зависимостей в каждой процедуре в модуле объекта (бизнес-логика) добавлены также вызовы процедур изменения зависимых данных.
Здесь есть один подвох. Не всегда зависимости распространяются строго в одном направлении. Иногда могут возникать "обратные петли" зависимостей. Для обеспечения устойчивого кода таких зависимостей необходимо избегать. В случае возникновения петель нужно просто не добавлять вызов процедуры зависимых данных, а реализовывать обработку данных непосредственно на месте вызова текущей процедуры бизнес-логики.
Разделение процедур отображения и бизнес-логики
События формы и их обработка
В общем случае связь с данными в форме осуществляется через её реквизиты. Отношения данных между собой можно описать связью в реквизитах формы по владельцу и по типу. Однако указание только таких отношений недостаточно. Часто на практике встречаются зависимости заполнения одних реквизитов от изменения других. Графически такие зависимости можно представить в виде графа зависимостей.
Пример: при изменении договора контрагента необходимо установить валюту взаиморасчетов по договору, получить курс на дату, пересчитать валютную сумму, заполнить счет взаиморасчетов.
Рисунок 2. Визуальное представление распространения зависимостей реквизитов формы
Рисунок 3. Диаграмма зависимостей реквизитов
На рисунке 2 показана циклическая зависимость для реквизитов «Курс» и «Сумма (вал)» - это так называемые петли зависимостей. Реализация «петлей зависимости» в алгоритме выливается в бесконечный цикл и, как следствие, – в зависание или падение приложения. Как правило, ошибки такого рода быстро выясняются на этапе тестирования и код не зацикливается. На уровне бизнес-логики при возникновении циклических зависимостей необходимо выполнять обработку данных без вызова обработчиков зависимых данных (можно такую функциональность вынести в отдельную общую процедуру и вызывать из взаимно-зависимых процедур обработки).
Отображение формы
Все действия по формированию отображения формы я предлагаю выносить в отдельную процедуру «УправлениеВидимостьюДоступностью». Путь эта процедура будет в каждой форме, тогда при событиях изменения данных в форме обработчик события должен выполнить вызов этой процедуры в конце выполнения. В этой процедуре осуществляется управление видимостью и доступностью реквизитов, закладок, установка доступных типов реквизитов, настройка визуальных свойств, меню, списков выбора и т.д. Вызов процедуры «УправлениеВидимостьюДоступностью» следует также использовать в событиях формы: ПриОткрытии, ПриИзменении (см. пример в Листинг 1).
Важно, что в процедуре «УправлениеВидимостьюДоступностью» ни в коем случае не должны изменяться сами данные!
Изменения данных (бизнес-логика)
Введем разделение процедур обработки данных формы: процедуры изменения данных и процедуры обработки событий формы.
Процедура обработки события – процедура, вызываемая по событию из формы
Процедура изменения данных – процедура бизнес-логики, располагаемая в модуле объекта
Задачи автоматизации могут потребовать алгоритмическое создание документов и заполнение их реквизитов из бизнес-логики. Здесь нужно разделять бизнес-логику и интерфейс. Интерфейс задает видимые реквизиты на форме и обработку их событий (событий, инициируемых пользователем), а бизнес-логика должна отрабатывать само изменение данных, согласно зависимостям между ними. Изменение данных может быть инициировано не только из интерфейса, но и из кода бизнес-логики.
Ниже приведены примеры кода (см. Листинг 1, Листинг 2) для интерфейсной части и бизнес-логики. Обратите внимание, что код бизнес-логики реализован в модуле объекта и с использованием ключевого слова Экспорт, т.к. иначе процедуры модуля объекта не будут доступны в модуле формы.
Соглашения о наименованиях процедур
Ниже в табличке я привел некоторый вариант стандарта наименований процедур, необходимых для поддержки модели. Здесь я основное внимание уделил процедурам при событиях формы ПриИзменении и обновлении формы.
Модель |
|
Наименование процедуры |
Комментарий |
Процедуры модуля формы (паттерн Document-View) |
|
УстановитьВидимостьДоступность |
Общая процедура модуля формы, вызываемая по событиям формы ПриИзменении, ПриОткрытии. Вызов процедуры располагается в конце обработчика события. В процедуре настраивается видимость, доступность элементов интерфейса формы, а также визуальные свойства реквизитов, необходимые списки выбора, ограничения типа, меню и т.д. Данные объекта в процедуре не изменяются. |
ИмяРеквизитаПриИзменении |
процедура события реквизита формы ПриИзменении |
Процедуры бизнес-логики (паттерн Model) |
|
ПриИзмененииИмяРеквизита |
процедура события изменения данных объекта. Процедура относится к реализации бизнес-логики и располагается в модуле объекта. |
Вывод
В этой статье был рассмотрен паттерн проектирования MVC в приложении 1С. Изначально я не планировал рассмотрение именно MVC, основной мой мотив был в подаче метода разработки надежных интерфейсов 1С. Однако программисты 1С – это все же практики программирования и каждого из Вас есть свой подход. Этот подход Ваш и он также работает. Теория же нам необходима, когда мы упираемся в ограничения: обеспечение скорости, надежности разработки, стоимость сопровождения. Надеюсь, предложенный подход в статье оказался для Вас полезным, а теоретическое обоснование интересным.
Возможно, может показаться, что я в статье так и не раскрыл тему. Однако основная моя цель была показать различие между функциональностью интерфейсной части и бизнес-логики. И если после прочтения статьи у Вас такое понимание различия сложилось, то я буду считать свою миссию выполненной.
Итак, основные тезисы:
- · Необходимо разделять процедуры интерфейса и бизнес-логики
- · Процедуры интерфейса располагаются в модуле формы
- · Процедуры бизнес-логики должны находиться в модуле объекта. Процедуры, вызываемые из формы используют ключевое слово Экспорт
- · Зависимости реализуются через каскадные вызовы зависимых процедур. Для предотвращения циклических вызовов необходимо реализовывать функциональность без вызова зависимых процедур.
Листинг 1. Пример реализации модели Document-View в модуле формы
// УстановитьВидимостьДоступность
// Процедура настраивает вид формы: видимость, доступность, типы значений реквизитов, списки выбора, заголовки
//
Процедура УстановитьВидимостьДоступность()
// Вывести в заголовке формы вид операции и статус документа (новый, не проведен, проведен)
//
РаботаСДиалогами.УстановитьЗаголовокФормыДокумента(Строка(ВидОперации) , ЭтотОбъект, ЭтаФорма);
// Установка доступности реквизитов
...
ЭтоВалютаРубли = Валюта = мВалютаРегламентированногоУчета;
ЭлементыФормы.Курс .Доступность = Не ЭтоВалютаРубли;
ЭлементыФормы.СуммаВал .Доступность = Не ЭтоВалютаРубли;
ЭлементыФормы.Кратность .Доступность = Не ЭтоВалютаРубли;
...
КонецПроцедуры
...
// Процедура - обработчик события "ПриОткрытии" формы.
//
Процедура ПриОткрытии()
...
УстановитьВидимостьДоступность();
КонецПроцедуры
...
/////////////////////////////////////////////////////////////////////////////////////////
// События формы ПриИзменении
//
Процедура СуммаПриИзменении(Элемент)
// Вызов процедур модуля
ПриИзмененииКурса(Валюта , Курс , Кратность , СуммаВал);
// Вызов процедуры отображения формы
УстановитьВидимостьДоступность();
КонецПроцедуры
Процедура КонтрагентПриИзменении(Элемент)
// Вызов процедур модуля
ПриИзмененииКонтрагента(Контрагент,
ДоговорКонтрагента, ВидДоговора, ВидРасчетов,
Счет, ЭтоСчетАванса(""), Валюта, Курс, Кратность, СуммаВал);
// Вызов процедуры отображения формы
УстановитьВидимостьДоступность();
КонецПроцедуры
Процедура ДоговорКонтрагентаПриИзменении(Элемент)
// Вызов процедур модуля
ПриИзмененииДоговораКонтрагента(Контрагент,
ДоговорКонтрагента, ВидДоговора, ВидРасчетов,
Счет, ЭтоСчетАванса(""), Валюта, Курс, Кратность, СуммаВал);
// Вызов процедуры отображения формы
УстановитьВидимостьДоступность();
КонецПроцедуры
Процедура ВалютаПриИзменении(Элемент)
// Вызов процедур модуля
КурсКратность = ОбщегоНазначения.ПолучитьКурсВалюты(Валюта, Дата);
Курс = КурсКратность.Курс;
Кратность = КурсКратность.Кратность;
ПриИзмененииКурса(Валюта, Курс, Кратность, СуммаВал);
// Вызов процедуры отображения формы
УстановитьВидимостьДоступность();
КонецПроцедуры
Процедура КурсПриИзменении(Элемент)
// Вызов процедур модуля
ПриИзмененииКурса(Валюта, Курс, Кратность, СуммаВал);
// Вызов процедуры отображения формы
УстановитьВидимостьДоступность();
КонецПроцедуры
Процедура СуммаВалПриИзменении(Элемент)
// Вызов процедур модуля
ПриИзмененииВалСуммы(СуммаВал, Курс, Кратность, СуммаВал);
// Вызов процедуры отображения формы
УстановитьВидимостьДоступность();
КонецПроцедуры
Листинг 2. Пример реализации модели бизнес-логики (Model). Процедуры бизнес-логики, вызываемые из модуля формы.
/////////////////////////////////////////////////////////////////////////////////////////
//
// Процедуры ПриИзменении
//
/////////////////////////////////////////////////////////////////////////////////////////
Процедура ПриИзмененииВалСуммы(СуммаВал, Курс, Кратность, СуммаВалютная) Экспорт
Если СуммаВалютная = 0 или Кратность = 0 Тогда
Возврат;
КонецЕсли;
// Каскадный вызов процедур зависимых реквизитов отсутствует.
// Значение реквизита изменяется непосредственно:
Курс = Сумма / (СуммаВалютная * Кратность);
КонецПроцедуры
Процедура ПриИзмененииКурса(Валюта, Курс, Кратность, СуммаВалютная) Экспорт
Если Не ЗначениеЗаполнено(Валюта) Тогда
Возврат;
КонецЕсли;
// Каскадный вызов процедур зависимых реквизитов отсутствует.
// Значение реквизита изменяется непосредственно:
СуммаВалютная = ОбщегоНазначения.ПересчитатьИзВалютыВВалюту(Сумма,
мВалютаРегламентированногоУчета, Валюта, 1, Курс, 1, Кратность);
КонецПроцедуры
Процедура ПриИзмененииКонтрагента(Контрагент, ДоговорКонтрагента, ВидДоговора, ВидРасчетов, Счет, ЭтоСчетАванса, Валюта, Курс, Кратность, СуммаВалютная) Экспорт
// Проверка на зависимость от реквизитов верхнего уровня
//
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Возврат;
КонецЕсли;
Если Не ЗначениеЗаполнено(ДоговорКонтрагента) И ЗначениеЗаполнено(Контрагент.ОсновнойДоговорКонтрагента) Тогда
ДоговорКонтрагента = Контрагент.ОсновнойДоговорКонтрагента;
КонецЕсли;
// Каскадный вызов процедур зависимых реквизитов
//
ПриИзмененииДоговораКонтрагента(Контрагент, ДоговорКонтрагента, ВидДоговора, ВидРасчетов, Счет, ЭтоСчетАванса, Валюта, Курс, Кратность, СуммаВалютная);
КонецПроцедуры
Процедура ПриИзмененииДоговораКонтрагента(Контрагент, ДоговорКонтрагента, ВидДоговора, ВидРасчетов, Счет, ЭтоСчетАванса, Валюта, Курс, Кратность, СуммаВалютная) Экспорт
// Проверка на зависимость от реквизитов верхнего уровня
//
Если Не ЗначениеЗаполнено(ДоговорКонтрагента) Тогда
Возврат;
КонецЕсли;
Если Не ЗначениеЗаполнено(Контрагент) ИЛИ ДоговорКонтрагента.Владелец<>Контрагент Тогда
ДоговорКонтрагента = "";
Возврат;
КонецЕсли;
...
// Каскадный вызов процедур зависимых реквизитов
//
ПриИзмененииКурса(Валюта, Курс, Кратность, СуммаВалютная);
КонецПроцедуры
Список источников
- http://ru.wikipedia.org/wiki/Model-View-Controller
- http://www.rsdn.ru/article/patterns/generic-mvc.xml
- http://www.rsdn.ru/article/patterns/modelviewpresenter.xml