Хочу рассказать о том, как я использую объектно-ориентированное программирование в 1С. Вернее его имитацию, т.к. в самом встроенном языке таких возможностей нет. Тем не менее, возможность создавать логически независимые, обособленные, самодостаточные фрагмены кода (да еще с инкапсулированными в них данными), весьма полезна.
Ведь их можно:
— использовать повторно внутри одного и того же проекта;
— легко и просто (не задумываясь) переносить из одного проекта в другой;
— передать кому-то еще, или выложить в Интернете для всеобщего использования как самостоятельное средство решения определенной задачи, которое соответственно также легко может быть кем-то скопировано и вставлено в собственный проект);
— имея класс, можно создать сразу несколько объектов (строить из них массивы, коллекции, списки и т.д.);
— еще какие-то плюсы, о которых я не знаю…
В этой статье будет показаны приемы имитации ООП средствами процедурно-ориентированного языка 1С.
Суть проблемы
Как известно, встроенный язык 1С не поддерживает ООП в полной мере. Есть стандартные встроенные классы платформы со своими полями, свойствами и методами. Можно создавать объекты этих классов:
МойМассив = Новый Массив;
и пользоваться ими так, как это делается в обычных универсальных языках программирования. Однако определять и реализовывать собственные классы во встроенном языке нельзя. В принципе, можно сказать, что платформа 1С не поддерживает объектно-ориентированное программирование.
Более того, от нескольких 1С-специалистов я слышал о том, что такая поддержка совершенно не нужна. Но это были именно «1С-программисты», т.е. люди которые всегда занимались разработкой только для платформы 1С, а не для компьютера, и не знакомы с классической «Computer Science». Те же, кто занимался разработкой на обычных универсальных языках и привык к возможностям ООП, столкнувшись с платформой 1С, часто испытывают сильные неудобства от невозможности строить архитектуру своей программы и вообще выражать свои мысли, свое видение реализации задачи, в виде объектной модели. Взамен ООП, встроенный язык предлагает вернуться к структурному программированию, которое также урезано по сравнению с классическими вариантами (например, языки Pascal и C). Нет возможности создавать несколько модулей общего вида (без форм) в том или ином объекте конфигурации. Это очень существенно, если например, делаешь внешнюю обработку (чтобы не привязываться к конкретной конфигурации, или просто не хочешь вносить в саму типовую конфигурацию изменения и снимать ее с поддержки) и соответственно никакая часть обработки не может быть вынесена в «общие модули», — весь функционал нужно помещать в один единственный модуль обработки. Нет возможности подключать конкретные модули внутри другого модуля (предложения uses или #include).
Uses СотрудникиОрганизаций, ТиповыеОтчеты; #include Справочники.Банки;
Впрочем, в данной статье речь пойдет не о модулях.
Итак, как же все-таки можно средствами структурного программирования реализовать хотя бы часть основных парадигм ООП?
С полиморфизмом и свойствами типа
property MyVar: Integer read GetMyVar write SetMyVar;
ничего не выйдет (их вообще-то тоже можно сделать, но синтаксически это будет выглядеть слишком наворочено, некрасиво и соответственно неудобно и неочевидно в применении). Про разграничение доступа (private, protected, public) тоже придется забыть. А вот инкапсуляцию и наследование (в режиме доступа public) можно сделать вполне прилично.
Суть решения
Идея в общем не нова, и ее можно почерпнуть, например, в архитектуре такого мощного суперпроекта из мира Unix, как графическая библиотека GTK+. Эта библиотека реализована на языке Си, но тем не менее по факту своей архитектуры является объектно-ориентированной (см. пример инициализации библиотеки GTK+ из вышеуказанной статьи в Википедии – из него станет понятно, что имеется в виду). Возможность реализовать ООП средствами языка, который ООП не поддерживает, кроется в принятии особых соглашений о кодировании исходников.
Ведь что такое класс? Класс – это определение данных и методов их обработки «в одном флаконе». А объект – это, по сути динамически созданный набор данных, который неявно передается в качестве параметра методам класса для того, чтобы методы могли обрабатывать эти самые данные. И если средствами языка, данные и методы запихнуть в «один флакон» не получится, то воспользуемся для этого условной надстройкой над возможностями языка – соглашениями о кодировании (Code Conventions).
Будем в качестве хранилища полей объекта использовать структуры (в Си – это встроенный тип данных, в 1С – встроенный класс – хеш-таблица).
Объект = Новый Структура; Объект. Вставить(«Поле1», 0); Объект. Вставить(«Поле2», “”); … Объект. Вставить(«ПолеN», Новый Массив);
В качестве методов будем использовать процедуры и функции, используя следующие соглашения об их наименовании:
Процедура ИмяКласса_ИмяМетода(СтруктураОбъект, …); Функция ИмяКласса_ИмяМетода(СтруктураОбъект, …);
Как видно вместо неявной передачи параметра-указателя на набор полей объекта, он переда-ется явно 1-м параметром. Следом идет обычный набор параметров метода.
Рассмотрим следующий пример:
//////////////////////////////////////////////////////////////////////////////// // Реализация класса ПочтовоеОтделение // Создание полей объекта в виде структуры. // // Параметры: // Нет. // Возврат: // Возвращает созданную структуру // Функция ПочтовоеОтделение_СоздатьОбъект() Экспорт // Все это конечно можно делать в конструкторе, но поскольку // в конструкторах классических языков, поддерживающих ООП, // это делается неявно, то лучше вынести создание полей в // отдельную функцию, чтобы не загромождать конструктор, // т.к. полей у реального (не демонстрационного) объекта // может быть много. Отделен = Новый Структура; // Общие параметры сущности Отделен.Вставить("Индекс", Неопределено); // Индекс почтового отделения Отделен.Вставить("Адрес", Адрес_Конструктор()); // Адрес почтового отделения Возврат Отделен; КонецФункции // Имитирует конструктор объекта. // // Параметры: // Индекс - строковое представление почтового индекса отделения // Адрес - ссылка на объект, содержащий адрес отделения // Возврат: // (Структура) - Структура с полями объекта // Функция ПочтовоеОтделение_Конструктор(Индекс, Адрес) Экспорт // Создать объект Отделен = ПочтовоеОтделение_СоздатьОбъект(); // Выполнить начальную инициализацию Отделен.Индекс = Индекс; Адрес_Assign(Отделен.Адрес, Адрес); Возврат Отделен; КонецФункции // Имитирует деструктор объекта - освобождает ресурсы. // // Параметры: // Отделен - ссылка на объект // Процедура ПочтовоеОтделение_Деструктор(Отделен) Экспорт // Во встроенном языке не нужно явно удалять ранее созданные объекты 1С. // Но здесь можно завершить работу с какими-либо внешними ресурсами. // Например: закрыть подключение к базе данных. Адрес_Деструктор(Отделен.Адрес); КонецПроцедуры // Возвращает полный почтовый адрес отделения. // // Параметры: // Отделен - ссылка на объект // Возврат: // (Строка) - Полный почтовый адрес отделения // Функция ПочтовоеОтделение_ПолучитьАдресОтделения(Отделен) Экспорт АдресОтделения = Отделен.Индекс + ", " + Адрес_ВСтроку(Отделен.Адрес); Возврат АдресОтделения; КонецФункции //////////////////////////////////////////////////////////////////////////////// // Реализация класса Адрес // Создание полей объекта в виде структуры. // // Параметры: // Нет. // Возврат: // Возвращает созданную структуру // Функция Адрес_СоздатьОбъект() Экспорт Адрес = Новый Структура; // Общие параметры сущности Адрес.Вставить("Регион", Неопределено); // Наименование региона Адрес.Вставить("Город", Неопределено); // Наименование города Адрес.Вставить("Улица", Неопределено); // Наименование улицы Адрес.Вставить("НомерДома", Неопределено); // Номер дома Возврат Адрес; КонецФункции // Имитирует конструктор объекта. // // Параметры: // НЕТ // Возврат: // (Структура) - Структура с полями объекта // Функция Адрес_Конструктор() Экспорт // Создать объект Адрес = Адрес_СоздатьОбъект(); Возврат Адрес; КонецФункции // Имитирует деструктор объекта - освобождает русурсы. // // Параметры: // Адрес - ссылка на объект // Процедура Адрес_Деструктор(Адрес) Экспорт КонецПроцедуры // Устанавливает основные атрибуты адреса. // // Параметры: // Адрес - ссылка на объект // Регион - название региона // Город - название города // Улица - название улица // НомерДома - номер дома // Процедура Адрес_УстАтриб(Адрес, Регион, Город, Улица, НомерДома) Экспорт Адрес.Регион = Регион; Адрес.Город = Город; Адрес.Улица = Улица; Адрес.НомерДома = НомерДома КонецПроцедуры // Возвращает строковое представление адреса. // // Параметры: // Адрес - ссылка на объект // Возврат: // (Строка) - Строковое представление адреса // Функция Адрес_ВСтроку(Адрес) Экспорт АдресСтрока = Адрес.Регион + ", " + "г. " + Адрес.Город + ", " + "ул. " + Адрес.Улица + ", " + "д. " + Адрес.НомерДома; Возврат АдресСтрока; КонецФункции // Копирует данные из объекта Адрес2 в объект Адрес1. // Все предыдущие данные Адрес1 будут утеряны. // // Параметры: // Адрес1 - ссылка на объект назначение // Адрес2 - ссылка на объект - источник // Процедура Адрес_Assign(Адрес1, Адрес2) Экспорт Адрес1.Регион = Адрес2.Регион; Адрес1.Город = Адрес2.Город; Адрес1.Улица = Адрес2.Улица; Адрес1.НомерДома = Адрес2.НомерДома КонецПроцедуры
Это был пример инкапсуляции. Наследование выглядит чуть более коряво.
//////////////////////////////////////////////////////////////////////////////// // Реализация класса Линия // Создание полей объекта в виде структуры. // // Параметры: // Нет. // Возврат: // Возвращает созданную структуру // Функция Линия_СоздатьОбъект() Экспорт Линия = Новый Структура; // Общие параметры сущности Линия.Вставить("X1", 0); // X-координата центра Линия.Вставить("Y1", 0); // Y-координата центра Возврат Линия; КонецФункции // Имитирует конструктор объекта. // // Параметры: // X1, Y1 - координаты начальной (центральной) точки линии // Возврат: // (Структура) - Структура с полями объекта // Функция Линия_Конструктор(X1, Y1) Экспорт // Создать объект Линия = Линия_СоздатьОбъект(); // Выполнить начальную инициализацию Линия.X1 = X1; Линия.Y1 = Y1; Возврат Линия; КонецФункции // Имитирует деструктор объекта - освобождает ресурсы. // // Параметры: // Линия - ссылка на объект // Процедура Линия_Деструктор(Линия) Экспорт КонецПроцедуры // Устанавливает атрибуты линии. // // Параметры: // Линия - ссылка на объект // X1, Y1 - координаты начальной (центральной) точки линии // Процедура Линия_УстАтриб(Линия, X1, Y1) Экспорт Линия.X1 = X1; Линия.Y1 = Y1; КонецПроцедуры // Возвращает длину линии. // // Параметры: // Линия - ссылка на объект // Возврат: // (Число) - Длина линии // Функция Линия_Длина(Линия) Экспорт Возврат 0; // Заглушка - данная функция должна быть переопределена в потомках КонецФункции //////////////////////////////////////////////////////////////////////////////// // Реализация класса Прямая - потомок класса Линия. // Создание полей объекта в виде структуры. // // Параметры: // Нет. // Возврат: // Возвращает созданную структуру // Функция Прямая_СоздатьОбъект() Экспорт Прямая = Линия_СоздатьОбъект(); // Общие параметры сущности Прямая.Вставить("X2", 0); // X-координата конечной точки Прямая.Вставить("Y2", 0); // Y-координата конечной точки Возврат Прямая; КонецФункции // Имитирует конструктор объекта. // // Параметры: // X1, Y1 - координаты начальной точки прямой // X2, Y2 - координаты конечной точки прямой // Возврат: // (Структура) - Структура с полями объекта // Функция Прямая_Конструктор(X1, Y1, X2, Y2) Экспорт // Создать объект Прямая = Прямая_СоздатьОбъект(); // Выполнить начальную инициализацию Прямая_УстАтриб(Прямая, X1, Y1, X2, Y2); Возврат Прямая; КонецФункции // Имитирует деструктор объекта - освобождает ресурсы. // // Параметры: // Прямая - ссылка на объект // Процедура Прямая_Деструктор(Прямая) Экспорт КонецПроцедуры // Устанавливает атрибуты объекта. // // Параметры: // Прямая - ссылка на объект // X1, Y1 - координаты начальной точки прямой // X2, Y2 - координаты конечной точки прямой // Процедура Прямая_УстАтриб(Прямая, X1, Y1, X2, Y2) Экспорт Линия_УстАтриб(Прямая, X1, Y1); Прямая.X2 = X2; Прямая.Y2 = Y2; КонецПроцедуры // Возвращает длину линии. // // Параметры: // Прямая - ссылка на объект // Возврат: // (Число) - Длина линии // Функция Прямая_Длина(Прямая) Экспорт // Найти длину проекции линии на ось X ДлинаX = Прямая.X2 - Прямая.X1; // Найти длину проекции линии на ось Y ДлинаY = Прямая.Y2 - Прямая.Y1; // Найти гипотенузу по теореме Пифагора ДлинаЛин = Sqrt(ДлинаX*ДлинаX + ДлинаY*ДлинаY); Возврат ДлинаЛин; КонецФункции //////////////////////////////////////////////////////////////////////////////// // Реализация класса Окружность (Окружн) - потомок класса Линия. // Создание полей объекта в виде структуры. // // Параметры: // Нет. // Возврат: // Возвращает созданную структуру // Функция Окружн_СоздатьОбъект() Экспорт Окружн = Линия_СоздатьОбъект(); // Общие параметры сущности Окружн.Вставить("R", 0); // радиус окружности Возврат Окружн; КонецФункции // Имитирует конструктор объекта. // // Параметры: // X, Y - координаты центра окружности // R - радиус окружности // Возврат: // (Структура) - Структура с полями объекта // Функция Окружн_Конструктор(X, Y, R) Экспорт // Создать объект Окружн = Окружн_СоздатьОбъект(); // Выполнить начальную инициализацию Окружн_УстАтриб(Окружн, X, Y, R); Возврат Окружн; КонецФункции // Имитирует деструктор объекта - освобождает ресурсы. // // Параметры: // Окружн - ссылка на объект // Процедура Окружн_Деструктор(Окружн) Экспорт КонецПроцедуры // Устанавливает атрибуты объекта. // // Параметры: // Окружн - ссылка на объект // X, Y - координаты центра окружности // R - радиус окружности // Процедура Окружн_УстАтриб(Окружн, X, Y, R) Экспорт Линия_УстАтриб(Окружн, X, Y); Окружн.R = R; КонецПроцедуры // Возвращает длину линии. // // Параметры: // Окружн - ссылка на объект // Возврат: // (Число) - Длина линии // Функция Окружн_Длина(Окружн) Экспорт ДлинаЛин = 2*3.14*Окружн.R; Возврат ДлинаЛин; КонецФункции
Естественно никаких виртуальных методов и полиморфизма здесь нет. Только переопределение одноименных методов в потомках. Одноименными они опять же являются с точки зрения логики разработки.
Резюме.
Как мы видим, описанный прием оформления исходников на языке 1С, расширяет возможности написанных таким образом фрагментов кода в плане наглядности, универсальности и переносимости.
В дальнейших публикациях я планирую рассказать о некоторых полезных штуках (можно сказать, инструментах), которые можно сразу взять и использовать в своих 1С-программах. Их код будет оформлен как раз в виде таких вот классов.
Скачать тексты рассмотренных в статье примеров (обработка для 1С 8.2) можно здесь.
Всем удачи. До встречи.
P.S.: В процессе подготовки этой статьи наткнулся на описание аналогичного подхода на портале «Инфостарт». Написано, кажется в 2012-м году — примерно в это же время данный метод стал использовать и я. Поскольку в указанной статье есть существенные отличия (автор предлагает унифицировать вызов методов и доступ к свойствам класса через функции-обертки, что как бы более технологично, но на мой взгляд менее наглядно), то я решил опубликовать и собственный вариант технологии.
ссылка на оригинал статьи http://habrahabr.ru/post/271731/
Добавить комментарий