Pull to refresh

Что такое объект

Reading time5 min
Views15K

TL;DR

Всем привет!

Недавно работал над задачей. Нужно было получить из сети некоторые объекты по REST и обработать.

Ну все вроде бы ничего сложного. Загрузил, спарсил, вернул. Ок. Затем нужно было полученный массив обработать. Вкратце, по особой логике просуммировать некоторые поля - это могла быть строка, число или null. Начал делать как обычно: создал переменную sum, начал цикл for, в нем начал заниматься основной логикой. Закончил.

Продолжил кодить. Хоба! Эта же логика. Не стал копипастить, вынес в отдельную функцию. Тоже все хорошо.

Начал заниматься 3 задачей. Объединить результаты нескольких вычислений. Опять циклом начал перебирать. Но тут появилась мысль:

“А что, если создать для этого отдельный объект?”

Да нет, чушь! Ведь не существует в реальном мире ОбъединителяКакогоТоРезультата. Но что, если сделать? Попробуем.

Какого!!!?? Почему все вмиг стало так просто? Передал в конструктор нужные объекты и сделал методы, которые применяли свою логику к содержащимся в них объектам. Всего-лишь несколько строчек! Почему я так раньше не делал?

Я раззадорился. Начал видеть объекты везде. Это очень удобно: не нужно смотреть на каждый фрагмент кода с мыслью “а было ли это где нибудь раньше?”. А как тестировать легче стало!

Тут до меня дошло, что было со мной не так: 

Я не разграничивал объекты реального мира и объекты в понимании ООП.

Объекты ООП != Объекты реального мира

Наверное главной моей ошибкой был недостаток практики: я много интересовался, читал, смотрел, но до кодирования руки не доходили. Поэтому, к моменту того события в моей голове было только 3 паттерна использования объектов:

  • DTO

  • Объекты из реального мира

  • Объекты, реализующие какой-то интерфейс (обычно для запросов по сети, для использования в DI контейнера)

Оглядевшись назад понял, что все дороги вели именно к такому мышлению:

  • В вузе нас учили ООП по каким-то моделям типа: “Вот это объект Человек. У него есть атрибуты Имя и Возраст”, а когда дело доходило до программирования, никто не смотрел как мы пишем код. Получалась каша из императивного программирования и набросков объектов.

  • Во всяких обучающих ресурсах (видео, книги, курсы) дают слишком простые примеры. Примеры слишком прямолинейные (как в выше перечисленном вузе). Не дают почувствовать мощь объектов.

  • Если были задачи, то слишком простые. Не тот уровень сложности, чтобы действительно над чем-то задуматься (например, приевшийся калькулятор). Они не показывали, что объекты могли бы решить многие проблемы.

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

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

Диаграммы мешают в понимании ООП

Но что насчет популярных инструментов проектирования? Нотаций. Наверное все видели различные UML диаграммы. Диаграмму классов так наверное любой программист должен был видеть хоть раз.

Взято с https://medium.com/@uferesamuel/uml-class-diagrams-the-simple-approach-eee2d1ffc125
Взято с https://medium.com/@uferesamuel/uml-class-diagrams-the-simple-approach-eee2d1ffc125

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

Взято с https://online.visual-paradigm.com/diagrams/templates/chen-entity-relationship-diagram/see-doctor-erd-chen-notation/
Взято с https://online.visual-paradigm.com/diagrams/templates/chen-entity-relationship-diagram/see-doctor-erd-chen-notation/

Поразмыслив, я понял 3 вещи:

  1. ER диаграмма ничего не имеет общего с ООП - это инструмент для бизнес-анализа. Я не обязан создавать такие же классы, как и на этой диаграмме. Кто мне такое сказал? 

  2. UML показывает высокоуровневую структуру программы: кто в ней есть и что они должны делать/иметь. Т.е. что делать, а не как делать. Реализация ложится на плечи программиста (спойлер, это будут методы на 100+ строк из циклов, условий и других прелестей)

  3. Многие нотации ориентированы для простого понимания концепций программы - из каких компонентов состоит. Ничто не мешает нам вместо классов передавать массивы 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;
    }
}

Как же стало удобно! Все находится рядом, сигнатуры стали короче и поддержка автодополнения в подарок - прелесть!

Выводы

Что я вынес из всего этого?

  1. Объект это не концепция реального мира. Можно сделать объект который имеет имя, атрибуты, поведение, как у объекта реального мира, сделать максимально похожим, но это НЕ ОБЪЕКТ РЕАЛЬНОГО МИРА. Надо прекратить думать в данном ключе!

    Объект - это (всего лишь) данные и функции, ассоциированные с ними 

  2. На каждый блок с логикой (цикл, последовательность условий и т.д.) я смотрю с мыслью: “Нельзя ли вынести это в отдельный объект?”

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

P.S. Я не радикал, а за осмысленное и прагматичное использование объектов: для тривиальной логики можно оставить циклы, разрешаю)

Only registered users can participate in poll. Log in, please.
Эта мысль банальна или хороший совет?
39.33% Хороший совет59
60.67% Банальщина91
150 users voted. 72 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 23: ↑12 and ↓11+1
Comments81

Articles