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

от автора

Дисклеймер: все нижеописанное относится к использованию ООП в бизнес-приложениях и я бы хотел вынести за скобки применение ООП для описания типов вроде 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("List");     }      public ActionResult List()     {         return this             .View("List");     } }

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


ссылка на оригинал статьи https://habr.com/ru/post/715606/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *