Возникла задача в которой было необходимо работать с объектами (не объекты метаданных) у которых есть множество одинаковых свойств и методов, но есть и свои специфические как свойства так и методы. Самым простым способом решения является ООП, но в 1С нет возможности работать с классами, инкапсуляцией, наследованием, полиморфизмом и так далее. Прочитал немногочисленные заметки и зарисовки, которые есть на этом сайте о том как предлагают реализовать подобные решения, но везде были свои "НО". Многие пишут, что не видят в ООП для 1С необходимости - это их дело, хотя и неосознанно они работают в "lite" версии ООП. Недостатки есть и в этой методике, которую хотелось предложить на Ваше обсуждение.
Для начала нам необходимо объединить данные и методы, этот процесс называется инкапсуляцией. Давайте посмотрим на текст следующего общего модуля:
Общий модуль "Класс_ФизическоеЛицо"
Функция Конструктор() Экспорт
МойОбъект = Новый Структура("Фамилия, Имя, _Пол", "", "", "");
МойОбъект.Вставить("ф", ЭтотОбъект);
Возврат МойОбъект;
КонецФункции
Функция Обращение(МойОбъект) Экспорт
Возврат МойОбъект.Фамилия+", "+МойОбъект.Имя;
КонецФункции
Функция Пол(МойОбъект, Пол="") Экспорт
Если НЕ ПустаяСтрока(Пол) Тогда
МойОбъект._Пол = Пол;
КонецЕсли;
Возврат МойОбъект._Пол;
КонецФункции
Здесь видно, что в качестве рабочей лошадки используется "Структура", хотя на ее место вполне подходит и "Соответствие". Первый недостаток заключается в том, что для вызова метода объекта необходимо использовать какое либо имя свойства для определения ссылки на модуль. Я для себя определил этим ключевым словом букву "ф". Но, методы и свойства чудесным образом объединились и теперь находятся в одной структуре (объекте) ссылку на который можно передавать и использовать почти в любом месте. Второй недостаток заключается в том, что контекст структуры не передается при вызове метода из этой структуры. Следовательно, необходимо первым параметром передать саму структуру. По этому все методы которые работают с данными объекта первым параметром получают саму структуру. Этот параметр я назвал «МойОбъект» (ну просто по тому что ключевое слово «ЭтотОбъект» занято). Третий недостаток заключается в том, что нет возможности сделать скрытыми свойства. Для себя я определил, что скрытыми будут те свойства, которые начинаются с символа «_» (подчеркивание) или их можно перенести в отдельное свойство (например, с именем «Скрытые»), состоящее из структуры только скрытых свойств. Что касается методов, то тут ситуация не такая однозначная. Ведь если процедуре или функции модуля не установить ключевое слово «Экспорт» то она будет недоступна из внешнего модуля, но она не будет доступна и для наследника. По этому если Вам необходимо использовать скрытый метод у наследников начинайте его так же как и скрытые свойства со знака «_» подчеркивания.
И так с инкапсуляцией мы разобрались. Теперь нам нужно решить вопрос с самым главным – наследованием. Смотрим следующий текст модуля:
Общий модуль "Класс_Сотрудник"
Функция КлассРодитель()
Возврат Класс_ФизическоеЛицо.ЭтотОбъект;
КонецФункции
Функция Конструктор() Экспорт
МойОбъект = КлассРодитель().Конструктор(); // Родительский конструктор
МойОбъект.Вставить("Должность", ""); // Добавляем новое свойство
МойОбъект.Вставить("ф", ЭтотОбъект); // Заменяем модуль набора методов
Возврат МойОбъект;
КонецФункции
Функция ОбращениеПоШтатке(МойОбъект) Экспорт // Новый метод этого класса
Возврат МойОбъект.Должность+" - "+МойОбъект.Ф.Обращение(МойОбъект);
КонецФункции
Функция Пол(МойОбъект, Пол=Неопределено) Экспорт // Модифицированный метод
Если (Пол<>Неопределено) и (НЕ (Пол="М" или Пол="Ж")) Тогда
Сообщить("Недопустимое значение при установке пола сотрудника!");
Возврат Неопределено;
КонецЕсли;
Возврат КлассРодитель().Пол(МойОбъект, Пол); // Вызываем родительский метод
КонецФункции
Функция Обращение(МойОбъект) Экспорт // Наследованный метод
Возврат КлассРодитель().СтрокаФИО(МойОбъект);
КонецФункции
Обратите внимание - каждый класс располагается в отдельном общем модуле. Теперь давайте смотреть, что у нас получилось. Конструктор вызывает родительский конструктор и получает от него все свойства, которые были определены в родительском классе, а если он вызывает своего родителя мы получим и свойства прародителя.. Четвертым недостатком является то, что если вы полностью переопределяете конструктор дочернего класса, то вы должны самостоятельно позаботиться о создании всех свойств, которые создаются родительским конструктором. Пятым недостатком и на мой взгляд самым существенным является то, что ВСЕ методы которые использует потомок необходимо объявлять в модуле потомка (в примере это функция «Обращение»). Небольшой ложкой меда здесь служит то, что при последующем или повторном наследовании эти методы можно перенести потомку простым копированием. Этот же недостаток заставляет отслеживать самостоятельно создание новых методов и копирование кода наследования в дочерние классы.
-
Но в результате мы получили объектный полиморфизм.
Давайте посмотрим, как будут работать наши классы в прикладном решении:
-
Использование в прикладном решении:
Сотрудник = Класс_Сотрудник.Конструктор();
Сотрудник.Фамилия = "Иванов";
Сотрудник.Имя = "Иван";
Сотрудник.Должность = "Программист 1С";
Клиент = Класс_ФизическоеЛицо.Конструктор();
Клиент.Фамилия = "Петров";
Клиент.Имя = "Петр";
Сообщить(Сотрудник.ф.Обращение(Сотрудник)); // Иванов, Иван
Сообщить(Клиент.ф.Обращение(Клиент)); // Петров, Петр
Сообщить(Сотрудник.ф.ОбращениеПоШтатке(Сотрудник)); // Программист 1С - Иванов, Иван
Сотрудник.ф.Пол(Сотрудник, "Н"); // Недопустимое значение при установке пола сотрудника!
Сотрудник.ф.Пол(Сотрудник, "М");
Сообщить(Сотрудник.ф.Пол(Сотрудник)); // М
Как видно из примера финальный код почти похож на стандартный ОПП (за исключением буквы «ф» перед методом и передачей самого объекта в качестве первого параметра) и такой подход можно смело назвать Подобием Объектно Ориентированного Программирования в 1С (ПООПс)
Если для Вашей задачи описанные недостатки будут малозначимыми, то вполне можно использовать такой подход для ее решения.