gifts2017

ООП. Инкапсуляция, часть 3.

Опубликовал fez (fez) в раздел Программирование - Теория программирования

Заключительная серия размышлений об инкапсуляции в 1С.


Более сложный пример инкапсуляции.

В каталоге плохих запахов кода Мартина Фаулера есть такой запах, который называется
"Связанные данные" (Data clumps).

Начинающие рефактореры редко обращают на него внимание, и очень зря. Ибо для того,
чтобы научиться писать хороший обектно-ориентированный код нужно выработать
особенно тонкое чутье к этому запаху. Ибо он устраняется с помощью рефакторинга
"Выделение класса" (Extract class).

Что это такой за запах?

Это когда у нас есть некоторый набор данных, довольно тесно связанных друг с другом.
Когда некоторый набор данных неразрывно путешествует друг за другом из метода в метод,
из функции в функцию. Когда этот набор невозможно разделить на составляющие - это
верный признак того, что из этих данных нужно составить новый класс.

Нужно ИНКАПСУЛИРОВАТЬ эти данные в класс.

А методы, в которые эти данные передавались как параметры - станут первыми кандидатами
на то, чтобы стать методами данного класса.

Ведь что такое класс в самом общем смысле этого слова? Это некоторые данные, и методы
работы с ними. Данные первичны.

Пример.

Есть у меня достаточно хитрая система расчета зарплаты. И в рамках этой системы мне
довольно часто нужно знать коэффициент выполнения менеджером плана по бренду. А так
же коэффициент выполнения менеджером общего плана продаж. Естественно, эти кэффициенты
меняются каждый месяц. И у каждого менеджера эти коэффициенты разные.

Соответственно, у нас есть плотная связка данных: Менеджер + Месяц. И пара методов для
работы с ними: КВПП_Общий() и КВПП_Бренда(Бренд). Обратите внимание, мне нет надобности
передавать в свои методы менеджера и месяц в качестве параметров. Эти данные уже там
есть. Они инкапсулированы в классе.

Даже создание таких маленьких классов (два параметра и два метода) позволяет довольно
сильно разгрузить код от подробностей. А говорят, что иногда такие наборы данных
могут довольно сильно разрастись, и что в таком случае подобная инкапсуляция - это
верное средство от расстройства рассудка. Охотно верю.

См. также

Подписаться Добавить вознаграждение
Комментарии
2. Олег Пономаренко (O-Planet) 12.12.08 06:50
Нююю... К третьей части тема раскочегарилась. Имхо, автор ввел по-большому счету свое понимание, и даже почти свое определение инкапсуляции в этой серии статей. Оно не стандартное, и это - смелый ход. Если тему развить соответствующим образом, то, возможно, оно и имеет право на жизнь. Считаю, что минусом статей является нечеткое позиционирование, что все описанное не относится к стандартной 1С, а к ее расширению в 1С++. Но скомпенсирую свой минус по первой статье плюсом. Строг. Потому что про ООП очень люблю читать и обсуждать. Я бы про ООП в 1С написал все не так. Начал бы от стандартной 1С и реализации ООП в ней, а потом бы перешел на 1С++.
3. Артур Аюханов (artbear) 12.12.08 06:55
(2) Цитата: "Начал бы от стандартной 1С и реализации ООП в ней"
В штатном 1С, собственно, нет ООП :)
Точнее так - сами внутренности 1С построены на ООП, для клиентского кода предоставляются спец.объекты ООП, но нет возможности создавать собственные классы/объекты и именно это не дает 1С считаться ООП-средой или языком :(

PS я также люблю порассуждать/почитать/пообсуждать по ООП, рефакторингу, построению кода :)
4. Евгений Мартыненков (JohnyDeath) 12.12.08 10:16
А сами методы КВПП_Общий() и КВПП_Бренда(Бренд) можно как-то подробнее описать?
5. fez (fez) 12.12.08 12:48
(2) Про позиционирование.
Считаю, что 1С++ уже достаточно давно присутствует на рынке. Так что можно считать, что все интересующиеся ООП, применительно к 7.7, уже давно знают, что использование 1С++ является самым простым и эффективным способом получить это самое ООП в этих самых 7.7. И не просто знают - для них это очевидно и естественно.

А остальным одинэсникам мои графоманские потуги будут неинтересны. Это же не баян про ночь, проведеную в серверном шкафу.

Хотя может быть ты и прав, и можно было бы одной фразой упомянуть об этом в самом начале.
6. fez (fez) 12.12.08 12:52
(4) Вот весь код класса. Методы почему-то не КВПП*, а КВПО*, но это неважно :)

===============================
Перем НачалоМесяца, КонецМесяца;

Перем КэшКВПО;

Перем Менеджер;

Процедура __Инит__(ВыбДата, ВыбМенеджер) Экспорт

НачалоМесяца = НачМесяца(ВыбДата);
КонецМесяца = КонМесяца(ВыбДата);

Менеджер = ВыбМенеджер;

КэшКВПО = "Не посчитан";

КонецПроцедуры


Функция КВПО_Бренда(Бренд) Экспорт

Если Бренд = Константа.БрендБезПлана Тогда
Возврат 1; // Bug 3973
КонецЕсли;
Если Бренд = Константа.БрендКонсультации Тогда
Возврат 1; // Bug 3976
КонецЕсли;
Если Бренд = Константа.БрендРаспродажа Тогда
Возврат 1; // Bug 4223
КонецЕсли;

Период = СоздатьОбъект("Справочник.ПериодыПланирования");
Если Период.НайтиПоРеквизиту("НачДата", НачалоМесяца, 1) = 0 Тогда
Возврат 1;
КонецЕсли;

ПП = СоздатьОбъект("Справочник.ПланыПродаж");
ПП.ИспользоватьВладельца(Период.ТекущийЭлемент());
ПП.ВыбратьЭлементыПоРеквизиту("Менеджер", Менеджер, 1, 0);
Пока ПП.ПолучитьЭлемент() = 1 Цикл
Если ПП.Бренд = Бренд Тогда
Если ПП.РучнаяПравка = 1 Тогда
Возврат ПП.ИзмененныйКоэффициентВыполнения;
КонецЕсли;

Возврат глКоэффициентБренда(ПП.ПлановаяСуммаПродаж, ПП.ДостигнутаяСуммаПродаж);
КонецЕсли;
КонецЦикла;

Возврат 1;
КонецФункции

Функция РассчитатьКВПО_Общий() Экспорт
Период = СоздатьОбъект("Справочник.ПериодыПланирования");
Если Период.НайтиПоРеквизиту("НачДата", НачалоМесяца, 1) = 0 Тогда
Возврат 1;
КонецЕсли;

СуммаПлана = 0;
СуммаРезультата = 0;

ПП = СоздатьОбъект("Справочник.ПланыПродаж");
ПП.ИспользоватьВладельца(Период.ТекущийЭлемент());
ПП.ВыбратьЭлементыПоРеквизиту("Менеджер", Менеджер, 1, 0);
Пока ПП.ПолучитьЭлемент() = 1 Цикл
Если (ПП.Бренд = Константа.БрендБезПлана) И
(ПП.РучнаяПравка = 1) Тогда
Возврат ПП.ИзмененныйКоэффициентВыполнения;
КонецЕсли;

СуммаПлана = СуммаПлана + ПП.ПлановаяСуммаПродаж;
СуммаРезультата = СуммаРезультата + ПП.ДостигнутаяСуммаПродаж;
КонецЦикла;

Если СуммаПлана = 0 Тогда
Возврат 1;
КонецЕсли;
Если СуммаРезультата / СуммаПлана > 0.8 Тогда
Возврат 1;
КонецЕсли;

Возврат 0;
КонецФункции

Функция КВПО_Общий() Экспорт

Если КэшКВПО = "Не посчитан" Тогда
КэшКВПО = РассчитатьКВПО_Общий();
КонецЕсли;
Возврат КэшКВПО;

КонецФункции
7. fez (fez) 12.12.08 12:57
а в комментарий как-нибудь можно отформатированный код засунуть?
8. Артур Аюханов (artbear) 12.12.08 13:08
(7) Как обычно, [code1] и [/code1] - 1 убрать :)
9. fez (fez) 12.12.08 14:21
Код
Перем НачалоМесяца, КонецМесяца;

Перем КэшКВПО;

Перем Менеджер;

Процедура __Инит__(ВыбДата, ВыбМенеджер) Экспорт
   
   НачалоМесяца = НачМесяца(ВыбДата);
   КонецМесяца = КонМесяца(ВыбДата);
   
   Менеджер = ВыбМенеджер;
   
   КэшКВПО = "Не посчитан";
   
КонецПроцедуры

   
Функция КВПО_Бренда(Бренд) Экспорт

   Если Бренд = Константа.БрендБезПлана Тогда
      Возврат 1; // Bug 3973
   КонецЕсли;
   Если Бренд = Константа.БрендКонсультации Тогда
      Возврат 1; // Bug 3976
   КонецЕсли;
   Если Бренд = Константа.БрендРаспродажа Тогда
      Возврат 1; // Bug 4223
   КонецЕсли;
   
   Период = СоздатьОбъект("Справочник.ПериодыПланирования");
   Если Период.НайтиПоРеквизиту("НачДата", НачалоМесяца, 1) = 0 Тогда
      Возврат 1;
   КонецЕсли;
   
   ПП = СоздатьОбъект("Справочник.ПланыПродаж");
   ПП.ИспользоватьВладельца(Период.ТекущийЭлемент());
   ПП.ВыбратьЭлементыПоРеквизиту("Менеджер", Менеджер, 1, 0);
   Пока ПП.ПолучитьЭлемент() = 1 Цикл
      Если ПП.Бренд = Бренд Тогда
         Если ПП.РучнаяПравка = 1 Тогда
            Возврат ПП.ИзмененныйКоэффициентВыполнения;
         КонецЕсли;
         
         Возврат глКоэффициентБренда(ПП.ПлановаяСуммаПродаж, ПП.ДостигнутаяСуммаПродаж);
      КонецЕсли;
   КонецЦикла;
   
   Возврат 1;
КонецФункции

Функция РассчитатьКВПО_Общий() Экспорт
   Период = СоздатьОбъект("Справочник.ПериодыПланирования");
   Если Период.НайтиПоРеквизиту("НачДата", НачалоМесяца, 1) = 0 Тогда
      Возврат 1;
   КонецЕсли;
   
   СуммаПлана = 0;
   СуммаРезультата = 0;
   
   ПП = СоздатьОбъект("Справочник.ПланыПродаж");
   ПП.ИспользоватьВладельца(Период.ТекущийЭлемент());
   ПП.ВыбратьЭлементыПоРеквизиту("Менеджер", Менеджер, 1, 0);
   Пока ПП.ПолучитьЭлемент() = 1 Цикл
      Если (ПП.Бренд = Константа.БрендБезПлана) И
          (ПП.РучнаяПравка = 1) Тогда
         Возврат ПП.ИзмененныйКоэффициентВыполнения;
      КонецЕсли;
      
      СуммаПлана = СуммаПлана + ПП.ПлановаяСуммаПродаж;
      СуммаРезультата = СуммаРезультата + ПП.ДостигнутаяСуммаПродаж;
   КонецЦикла;
   
   Если СуммаПлана = 0 Тогда
      Возврат 1;
   КонецЕсли;
   Если СуммаРезультата / СуммаПлана > 0.8 Тогда
      Возврат 1;
   КонецЕсли;
   
   Возврат 0;
КонецФункции

Функция КВПО_Общий() Экспорт
    
   Если КэшКВПО = "Не посчитан" Тогда
      КэшКВПО = РассчитатьКВПО_Общий();
   КонецЕсли;
   Возврат КэшКВПО;
   
КонецФункции
Показать полностью
10. fez (fez) 12.12.08 14:22
11. Дмитрий Воробьев (vde69) 12.12.08 18:16
ну плюсану, тоже в качестве баланса за минус в первой части.

тем-не менее есть одно маленькое замечание/вопрос:
1с++ нарушаеть лицензионные соглашение ?????

ИХМО - нарушает, по этому и не так 1с++ распространена.

ps
статья - ни о чем, по сколько в ней изложены основы которые и так всем понятны, но мало примеров реализации. Короче можно было бы все обьеденить в одну, и еще добавить столько-же. Кроме того кто не пользует 1с++ она вообще имет только теоретический смысл...
12. fez (fez) 12.12.08 19:31
(11) Практика показывает, что как раз с основами самые большие проблемы и возникают :)

И отвечу маленьким вопросом на маленький вопрос. А у 7.7 разве есть лицензионное соглашение? Дадите почитать?
13. Дмитрий Воробьев (vde69) 12.12.08 19:54
(11) у Альфа спрашивай, он от туда цитировал чего-то.
Кстати видимо и его защита тоже попадает :)

А по поводу основ, так ты-бы собрал все статью, связал единой мыслью развития идеи, добавил примеров и в конце вывод забабахал (ведь видно что можешь), вот это да, а так не серьезно, огрызки какие-то.

зы
кстати например я ни разу 1с++ не юзал (это к вопросу о распространенности). Хотя конечно ООП мне знакомо не по наслышки.
14. fez (fez) 12.12.08 22:10
(13) Про большую статью. Это много времени надо. Особенно чтобы вывод написать. А так есть несколько маленьких разрозненных мыслей - почему бы с обществом и не поделиться?

Поскольку мне понравилась реакция читателей - я намерен продолжать. Про полиморфизм мне всяко есть чего сказать. Ну и про наследование тоже наскребу наверное немного.
15. Олег Пономаренко (O-Planet) 12.12.08 23:27
(2) >> В штатном 1С, собственно, нет ООП :)
>> Точнее так - сами внутренности 1С построены на ООП, для клиентского кода предоставляются спец.объекты ООП, но нет возможности создавать собственные классы/объекты и именно это не дает 1С считаться ООП-средой или языком :(

А вот тут, почитав эту подборку статей, не согласен. Если пользоваться терминологией автора и рассматривать упрятывание чего-либо в глобальную функцию с осмысленным названием, как инкапсуляцию, то под созданием собственных классов по праву можно понимать создание собственных документов на базовом классе "Документ". Чем не механизм наследования? Соответственно, копии моего класса - это реальные доки, созданные в пользовательском режиме. А вот полиморфизм надо еще поискать в версии 7.7...
16. fez (fez) 13.12.08 06:15
(15) А чего там искать? Документ.Провести(). Интерфейс одинаковый, поведение разное. Заказывали полиморфизм? Получите-распишитесь.
17. Олег Пономаренко (O-Planet) 13.12.08 20:53
(16) Вообще, да. Короче, любой предопределенный метод.
19. fez (fez) 14.12.08 19:09
(18) Я их передаю как параметр один раз - в метод __Инит__(). В 1С, к сожалению, нет конструкторов с параметрами, поэтому приходится немного извращаться.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа