Pull to refresh

ООП мертв, да здравствует ООП

Level of difficultyMedium
Reading time3 min
Views6K

Дисклеймер: все нижеописанное относится к использованию ООП в бизнес-приложениях и я бы хотел вынести за скобки применение ООП для описания типов вроде int, string и т.д.

Концепцию ООП часто применяют для отражения объектов реального мира в коде программ. Так объект реального мира "Пользователь" может быть описан в коде как класс "Пользователь", у которого есть поля: имя и адрес электронной почты, а действия, которые можно совершить над объектами реального мира, превращаются в методы класса: назначить администратором, удалить учетную и т.д.

public sealed class User
{
    public string Email { get; set; }
    
    public string Name { get; set; }

    public void Delete()
    {
...
    }

    public void SetAsAdministrator()
    {
...
    }
}

В простых учебных примерах такой подход позволяет познакомить начинающих программистов с основными инструментами ООП. Но когда дело доходит до более сложных примеров, кол-во методов в классе растет, а с ними и количество строк кода — код становится сложно читаемым. Но, главное, связь между объектами реального мира, когда изменение одного объекта влечет за собой изменение другого (удаление учетной записи пользователя, приводит к удалению учетной записи из всех списков где она была отмечена), сложно отразить в коде одного класса. Для меня апофеозом такого подхода стал проект eShopOnContainers, когда для решения, казалось бы, простой задачи, которая была решена уже тысячи раз, приходится писать огромное кол-во строк кода. Связь того, как изменение состояния одного объекта влияет на другой объект, прослеживается не явно, через очередь событий и несколько классов. Кол-во зависимостей, требующихся для инстанцирования одного класса, растет, что приводит к сложности использования такого класса как в приложении, так и в тестах.

Вдобавок к этому, в современных системах практически отпала потребность хранить состояние объектов в приложении, все состояние хранят специализированные системы: базы данных или внешние сервисы (которые в итоге хранят его в БД). Задача многих приложений сводится к отображению состояния сущностей в разных проекциях и предоставлению методов изменения состояния с учетом правил предметной области этих приложений. В общем виде это можно представить как методы получения данных для отображения части состояния объекта и методы для изменения состояния. Описание и хранение самого состояния в приложении не требуется.

В классах-проекциях (Data Transfer Object), которые передают данные между слоями приложения, не требуется иметь методов изменения состояния. Тогда возникает вопрос, где же хранить методы, позволяющие получить состояние или изменять его? Мы в Retail Rocket пришли к решению сделать по одному классу на каждую такую задачу. Те классы, которые относятся строго к одному объекту, можно сгруппировать в папке с именем этого объекта, а те, что, например, меняют состояние более одного класса, положить в папку с именем Services.

Зачем же тогда нужен ООП, если все приложение — это набор функций? Для локализации границ зависимостей функции. ООП позволяет элегантно описать зависимости, которые требуются "функции".

Вот пример "функции", которая меняет состояние системы

internal sealed class RemoveVerificationRequestHandler
    : IRemoveVerificationRequestHandler
{
    private readonly IMongoCollection<VerificationRequest> verificationRequestCollection;

    public RemoveVerificationHandler(
        IMongoCollection<VerificationRequest> verificationRequestCollection)
    {
        this.verificationRequestCollection = verificationRequestCollection;
    }


    public void Handle(
        VerificationRequestId verificationRequestId)
    {
...
    }
}

Для работы этой функции требуется 1 внешняя зависимость в виде интерфейса к конкретной коллекции в базе данных, ее мы и принимаем через конструктор и сохраняем в private поле.

Вот пример использования такой функции в MVC контроллере

public class VerificationListController
    : Controller
{
    private readonly IRemoveVerificationRequestHandler removeVerificationRequestHandler;

    public VerificationListController(
        IRemoveVerificationRequestHandler removeVerificationRequestHandler)
    {
        this.removeVerificationRequestHandler = removeVerificationRequestHandler;
    }

    public ActionResult RemoveVerificationRequest(
        VerificationRequestId verificationRequestId)
    {
        this
            .removeVerificationRequestHandler
            .Handle(verificationRequestId);

        return this
            .RedirectToAction(
              actionName: nameof(this.List));
    }

    public ActionResult List()
    {
        return this
            .View("List");
    }
}

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

Tags:
Hubs:
Total votes 6: ↑2 and ↓4-2
Comments91

Articles