ООП в 1С своими руками. Как имитировать свои классы и объекты, и зачем это нужно

от автора

image

Хочу рассказать о том, как я использую объектно-ориентированное программирование в 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/


Комментарии

Добавить комментарий

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