Исследуем применение ООП-подхода на примере сортировки коллекций. Вариантов для эмуляции объектов (экземпляров класса) средствами 1С мне видится всего два: с помощью объекта метаданных (например Обработка) и с помощью универсальной коллекции языка 1С (например Структура). Первый описан неоднократно, например, здесь. В этом варианте поля класса являются реквизитами обработки, а методы класса - функциями и процедурами в модуле объекта обработки. Остановимся более подробно на втором варианте.
Итак, возьмем для построения объекта Структуру, поля объекта будут соответствовать элементам структуры. Процедуры и функции для работы с такой структурой опишем в отдельном общем модуле, таким образом, этот общий модуль будет соответствовать классу ООП. Очевидно, что метод ничего не знает об объекте, к которому он принадлежит, поэтому объект, т.е. структуру, необходимо передавать в метод в качестве параметра. Вышесказанное не относится к статическим методам, которые не используют данные объекта и могут быть вызваны без создания экземпляра класса.
Создадим для примера класс (общий модуль) КлассБазовый, и определим в нем конструктор.
Функция СоздатьОбъект() Экспорт
Результат = Новый Структура;
Результат.Вставить("Класс",КлассБазовый);
Результат.Вставить("ТипОбъекта",Результат.Класс);
Возврат Результат;
КонецФункции
Функция ТипОбъекта(Объект) Экспорт
Возврат Объект.Класс;
КонецФункции
Таким образом, объект будет иметь одно поле Класс и метод получения значения этого поля - геттер ТипОбъекта(). Структура же, эмулирующая объект, кроме элемента Класс содержит еще и элемент с ключом ТипОбъекта и значением, равным ссылке на сам этот класс (общий модуль). Для чего это нужно? Предположим, что мы решили создать новый класс КлассНовый, наследник от КлассБазовый:
Функция СоздатьОбъект()Экспорт
// наследуем поле "Класс" и метод "ТипЗначения()" класса-родителя
Результат = КлассБазовый.СоздатьОбъект();
// Переопределим поле "Класс"
Результат.Вставить("Класс",КлассНовый);
Возврат Результат;
КонецФункции
В конструкторе мы переопределим поле Класс, но метод ТипОбъекта() нас вполне устраивает, нам переопределять его не надо. При создании экземпляра класса КлассНовый значение элемента структуры ТипОбъекта будет унаследовано от класса КлассБазовый и не будет переопределено в конструкторе. Это означает, что мы, глядя на объект класса КлассНовый, увидим, что метод ТипОбъекта() определен в классе (общем модуле) КлассБазовый:
НовыйОбъект = КлассНовый.СоздатьОбъект();
Модуль = НовыйОбъект.ТипОбъекта;
Сообщить(Модуль.ТипОбъекта(НовыйОбъект));
или, покороче:
НовыйОбъект = КлассНовый.СоздатьОбъект();
Сообщить(НовыйОбъект.ТипОбъекта.ТипОбъекта(НовыйОбъект));
Конструкция "НовыйОбъект.ТипОбъекта.ТипОбъекта(НовыйОбъект)" выглядит... м-м-м странно и наверняка вызовет язвительную усмешку у тру-ООП-программистов, но мы же проводим исследование, верно? Зато такой подход позволяет эмулировать позднее связывание, т.е. когда только в момент вызова определяется какой из методов вызывать, а также дает возможность эмулировать таблицу виртуальных методов - место хранения ссылок на методы классов. Таблица здесь не является таблицей в прямом смысле этого слова, она "размазана" по элементам структур разных классов.
Более подробный пример для класса КлассУпорядоченнаяКоллекция:
Функция СоздатьОбъект()Экспорт
// наследуем поля и методы класса-родителя
Результат = КлассБазовый.СоздатьОбъект();
// Поля
Результат.Вставить("Класс",КлассУпорядоченнаяКоллекция);
Результат.Вставить("Предмет");
// Абстрактные методы, которые не определены в этом классе
Результат.Вставить("ВывестиЭлемент");
// Новые методы
Результат.Вставить("Количество", Результат.Класс);
Результат.Вставить("Получить", Результат.Класс);
Результат.Вставить("Вывести", Результат.Класс);
Возврат Результат;
КонецФункции
Функция Количество(Объект)Экспорт
Возврат Объект.Предмет.Количество();
КонецФункции
Функция Получить(Объект, Индекс)Экспорт
Возврат Объект.Предмет.Получить(Индекс);
КонецФункции
Процедура Вывести(Объект)Экспорт
ВГраница = Объект.Количество.Количество(Объект) - 1;
Для Индекс = 0 По ВГраница Цикл
Элемент = Объект.Получить.Получить(Объект, Индекс);
Объект.ВывестиЭлемент.ВывестиЭлемент(Объект, Элемент);
КонецЦикла;
КонецПроцедуры
Предмет здесь - некая коллекция (Массив, СписокЗначений, ТаблицаЗначений), для которой определено отношение порядка, т.е. каждый элемент имеет свой порядковый номер - индекс.
Для этого класса определены очевидные методы Количество() и Получить(), а вот с методом Вывести() ситуация интереснее. Алгоритм вывода коллекции простой - в цикле от 0 до верхней границы получаем очередной элемент и выводим его. Вполне возможна ситуация, когда у какого-либо потомка появится свой, переопределенный метод Количество() или метод Получить(). Поэтому вызов этих методов происходит посредством обращения к элементам структуры-объекта, содержащим ссылки на общие модули. Методы, которые можно переопределить в классах-потомках называются виртуальными методами. Метод же ВывестиЭлемент() вообще не определен для этого класса (в этом общем модуле). Предполагается, что этот метод должен быть определен в классе-потомке (необязательно потомке первого уровня!). Такие методы, как ВывестиЭлемент(), называются абстрактными методами, а классы с такими методами - абстрактные классы.
Вернемся однако к нашей основной задаче - сортировке. Я не буду останавливаться на процессе проектирования, а сразу покажу результат:
Иерархия классов:
Наследование методов для КлассМассивЧисловой и КлассМассивСтруктур:
Здесь жирным шрифтом помечены методы, определенные или переопределенные в классе, серым цветом - унаследованные, курсивом - абстрактные методы.
Похожим образом определен и класс КлассСписокЗначений.
Процедура сортировки описана в абстрактном классе КлассУпорядоченнаяКоллекция, она использует приватный (не экспортный) метод СравнитьИПоменять()
Процедура Сортировать(Объект)Экспорт
ВГраница = Объект.Количество.Количество(Объект) - 1;
Для Индекс = 1 По ВГраница Цикл
Индекс2 = Индекс;
Пока Индекс2 > 0 Цикл
Если Не СравнитьИПоменять(Объект, Индекс2 - 1, Индекс2) Тогда
прервать;
КонецЕсли;
Индекс2 = Индекс2 - 1;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Функция СравнитьИПоменять(Объект, Знач Индекс1, Знач Индекс2)
Результат = Ложь;
Элемент1 = Объект.Получить.Получить(Объект, Индекс1);
Элемент2 = Объект.Получить.Получить(Объект, Индекс2);
Если Объект.СравнитьЭлементы.СравнитьЭлементы(Объект, Элемент1, Элемент2) Тогда
Объект.Установить.Установить(Объект, Индекс1, Элемент2);
Объект.Установить.Установить(Объект, Индекс2, Элемент1);
Результат = Истина;
КонецЕсли;
Возврат Результат;
КонецФункции
Методы СравнитьЭлементы() определены в классах-потомках последнего уровня, но это не мешает их использовать в процедуре Сортировать() у предка.
Метод СравнитьЭлементы() класса КлассМассивЧисловой:
Функция СравнитьЭлементы(Объект, Элемент1, Элемент2)Экспорт
Возврат Элемент1 > Элемент2;
КонецФункции
Что же у нас получилось в итоге такого моделирования ООП?
- Инкапсуляция - ну, что-то подобное есть: поля и методы сгруппированы и "принадлежат" отдельным сущностям. Однако никакой защиты, изоляции и сокрытия этих данных нет. К полям есть доступ отовсюду, можно даже удалить любое поле объекта. "Таблица виртуальных методов" не защищена от порчи.
- Наследование - определенно, есть. Поля наследуются, публичные методы - виртуальные, приватные методы не наследуются. Несложно реализовать множественное наследование - наследование от нескольких родителей.
- Полиморфизм - есть и даже, наверное, избыточен и бесконтролен. Благодаря отсутствию контроля типов поле может принимать значения разных типов в объектах-наследниках. Методы могут переопределяться без ограничений. В методах классов-наследников могут быть добавлены новые (необязательные) параметры.
- Абстракция - есть, и это, возможно, самое главное. В рассмотренном примере реализован алгоритм сортировки абстрактной коллекции, который не зависит от конкретной реализации вспомогательных методов получения, установки и сравнения отдельных элементов коллекции.
Что необходимо отметить. Получившийся код не является "красивым" и оптимальным, у него есть проблемы с клиент-серверной передачей контекста и, возможно, производительностью. Однако, в отличие от решений, предложенных в некоторых других публикациях, не используются дополнительные средства для обеспечения работы, вроде каких-нибудь общих функций для получения значений полей, или вызовов методов через Выполнить(). Таким образом, весь функционал класса независим и сосредоточен внутри класса. Обратите, кстати, внимание на схожесть применяемых приемов в этой и предыдущей статьях. В обоих случаях для сортировки разных сущностей в метод, осуществляющий сортировку, мы в качестве параметров передаем некую ссылку на вспомогательные методы, адаптированные для конкретной сущности. В предыдущей статье это - функция функционального программирования, содержащая ссылку на общий модуль, где определен алгоритм обработки, в этой публикации - объект, в котором есть элемент структуры со ссылкой на аналогичный общий модуль.
А теперь о картинке в заголовке статьи.
Мне кажется, она великолепно описывает попытки внедрения в 1С механизмов, для которых 1С не предназначена. Раскрашенные пальмы и бамбук могут заменить березовую рощу только в очень редких случаях (например когда заказчик рассматривает заросли с расстояния нескольких сотен метров). При этом надо учитывать дополнительные затраты на краску, кисти и работу. Поддержка тоже усложняется - необходимо все это регулярно подкрашивать. Так что же, получается, что всем этим вообще не стоит заниматься? Вовсе нет, напротив! Приобретается дополнительный опыт по работе с лакокрасочными материалами, работник получает некое морально-эстетическое удовлетворение, да и вообще, боец должен быть всегда занят. На мой взгляд, самый правильный подход - отличать деятельность, направленную на совершенствование навыков и получение положительных эмоций, от деятельности по созданию продуктов. В последнем случае главным аргументом должно являться соотношение результата, для получения которого нужен продукт, и затрат (денежных, временных, нематериальных). И тогда использование простых и надежных инструментов обычно становится предпочтительным.
Необходимо ли полноценное ООП в 1С? На мой взгляд, при разработке в мире 1С существует не так много задач, решение которых с использованием ООП давало бы существенный выигрыш по сравнению с обычным процедурным подходом. Из своего опыта, единственное, что могу припомнить - неплохо было бы иметь наследование форм или их частей с наследованием реквизитов, элементов и обработчиков. Но как совместить такое наследование с возможностью редактировать формы в конфигураторе и обеспечить надежность и удобство всего этого процесса, я не представляю - те зачатки наследования, которые уже есть, далеко не образец дружелюбного механизма. Хотелось бы, чтобы усилия разработчиков платформы были направлены прежде всего на совершенствование текущей предметно-процедурной методологии разработки, устранение методических ошибок, доработку недоделанных механизмов и исправление ошибок.
К статье приложена конфигурация с примерами сортировок числового массива, массива структур "Фамилия,Имя,Отчество" и списка значений (дата - представление даты). Работа проверялась на платформе 8.3.18.1363.
На этом всё. Как всегда, приветствуются замечания / дополнения / комментарии.
Предыдущая статья:
Следующая статья: