TL;DR
Всем привет!
Недавно работал над задачей. Нужно было получить из сети некоторые объекты по REST и обработать.
Ну все вроде бы ничего сложного. Загрузил, спарсил, вернул. Ок. Затем нужно было полученный массив обработать. Вкратце, по особой логике просуммировать некоторые поля — это могла быть строка, число или null. Начал делать как обычно: создал переменную sum, начал цикл for, в нем начал заниматься основной логикой. Закончил.
Продолжил кодить. Хоба! Эта же логика. Не стал копипастить, вынес в отдельную функцию. Тоже все хорошо.
Начал заниматься 3 задачей. Объединить результаты нескольких вычислений. Опять циклом начал перебирать. Но тут появилась мысль:
“А что, если создать для этого отдельный объект?”
Да нет, чушь! Ведь не существует в реальном мире ОбъединителяКакогоТоРезультата. Но что, если сделать? Попробуем.
Какого!!!?? Почему все вмиг стало так просто? Передал в конструктор нужные объекты и сделал методы, которые применяли свою логику к содержащимся в них объектам. Всего-лишь несколько строчек! Почему я так раньше не делал?
Я раззадорился. Начал видеть объекты везде. Это очень удобно: не нужно смотреть на каждый фрагмент кода с мыслью “а было ли это где нибудь раньше?”. А как тестировать легче стало!
Тут до меня дошло, что было со мной не так:
Я не разграничивал объекты реального мира и объекты в понимании ООП.
Объекты ООП != Объекты реального мира
Наверное главной моей ошибкой был недостаток практики: я много интересовался, читал, смотрел, но до кодирования руки не доходили. Поэтому, к моменту того события в моей голове было только 3 паттерна использования объектов:
-
DTO
-
Объекты из реального мира
-
Объекты, реализующие какой-то интерфейс (обычно для запросов по сети, для использования в DI контейнера)
Оглядевшись назад понял, что все дороги вели именно к такому мышлению:
-
В вузе нас учили ООП по каким-то моделям типа: “Вот это объект Человек. У него есть атрибуты Имя и Возраст”, а когда дело доходило до программирования, никто не смотрел как мы пишем код. Получалась каша из императивного программирования и набросков объектов.
-
Во всяких обучающих ресурсах (видео, книги, курсы) дают слишком простые примеры. Примеры слишком прямолинейные (как в выше перечисленном вузе). Не дают почувствовать мощь объектов.
-
Если были задачи, то слишком простые. Не тот уровень сложности, чтобы действительно над чем-то задуматься (например, приевшийся калькулятор). Они не показывали, что объекты могли бы решить многие проблемы.
В программе полно таких неявных объектов — служебных объектов: считают, фильтруют, агрегируют. Никогда не задумывался над тем, что практически любой for можно (наверное, даже лучше) заменить на объект, инкапсулирующий необходимую логику.
Пожалуй единственное, что меня ограничивало — идефикс, того, что объекты должны представлять концепции реального мира. Кто мне вообще это сказал?
Диаграммы мешают в понимании ООП
Но что насчет популярных инструментов проектирования? Нотаций. Наверное все видели различные UML диаграммы. Диаграмму классов так наверное любой программист должен был видеть хоть раз.

ER диаграммы тоже хороши — они слишком сильно сцеплены с реальным миром. Там почти все представляет объекты реального мира.

Поразмыслив, я понял 3 вещи:
-
ER диаграмма ничего не имеет общего с ООП — это инструмент для бизнес-анализа. Я не обязан создавать такие же классы, как и на этой диаграмме. Кто мне такое сказал?
-
UML показывает высокоуровневую структуру программы: кто в ней есть и что они должны делать/иметь. Т.е. что делать, а не как делать. Реализация ложится на плечи программиста (спойлер, это будут методы на 100+ строк из циклов, условий и других прелестей)
-
Многие нотации ориентированы для простого понимания концепций программы — из каких компонентов состоит. Ничто не мешает нам вместо классов передавать массивы object. Не нужно ориентироваться на них как на истину в первой инстанции.
В итоге заканчиваем, тем что имеем много объектов. Ура, ООП! А что внутри? Громадные циклы на десятки строк, множество флагов и if’ов — полная императивщина.
Да о чем я говорю?
Что же я понял? Например,
public interface IWorkingScheduleService { // Возвращает тип дня: рабочий, предпраздничный, праздничный, выходной int GetDayType(DateOnly date); }
// Количество рабочих часов на каждый день недели public class UserSchedule { public float Monday { get; set; } public float Tuesday { get; set; } public float Wednesday { get; set; } public float Thursday { get; set; } public float Friday { get; set; } public float Saturday { get; set; } public float Sunday { get; set; } }
Задача — посчитать общее время рабочих часов.
Банально, да? Давайте сделаем функции:
public static class ScheduleHelpers { public static float GetTotalWorkingHours(IWorkingScheduleService service, UserSchedule schedule, DateOnly from, DateOnly to) { // Какая-то логика return 0; } public static float GetTotalWorkingHoursWithoutPreholiday(IWorkingScheduleService service, UserSchedule schedule, DateOnly from, DateOnly to) { // Какая-то логика return 0; } public static float GetTotalHolidayWorkingHours(IWorkingScheduleService service, UserSchedule schedule, DateOnly from, DateOnly to) { // Какая-то логика return 0; } }
Но тут мы заметим общую начальную часть: IWorkingScheduleService service, UserSchedule schedule. Почему бы нам не вынести эту логику в отдельный объект?
public class WorkingScheduleCalculator { private readonly IWorkingScheduleService _service; private readonly UserSchedule _schedule; public WorkingScheduleCalculator(IWorkingScheduleService service, UserSchedule schedule) { _service = service; _schedule = schedule; } public float GetTotalWorkingHours(DateOnly from, DateOnly to) { // Какая-то логика return 0; } public float GetTotalWorkingHoursWithoutPreholiday(DateOnly from, DateOnly to) { // Какая-то логика return 0; } public float GetTotalHolidayWorkingHours(DateOnly from, DateOnly to) { // Какая-то логика return 0; } }
Как же стало удобно! Все находится рядом, сигнатуры стали короче и поддержка автодополнения в подарок — прелесть!
Выводы
Что я вынес из всего этого?
-
Объект это не концепция реального мира. Можно сделать объект который имеет имя, атрибуты, поведение, как у объекта реального мира, сделать максимально похожим, но это НЕ ОБЪЕКТ РЕАЛЬНОГО МИРА. Надо прекратить думать в данном ключе!
Объект - это (всего лишь) данные и функции, ассоциированные с ними -
На каждый блок с логикой (цикл, последовательность условий и т.д.) я смотрю с мыслью: “Нельзя ли вынести это в отдельный объект?”
-
Таким же образом, смотрю на функции, которые принимают одинаковые аргументы. Их всех можно объединить в объекты, атрибутами которых являются эти общие аргументы.
P.S. Я не радикал, а за осмысленное и прагматичное использование объектов: для тривиальной логики можно оставить циклы, разрешаю)
ссылка на оригинал статьи https://habr.com/ru/post/688348/
Добавить комментарий