Дисклеймер: все нижеописанное относится к использованию ООП в бизнес-приложениях и я бы хотел вынести за скобки применение ООП для описания типов вроде 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"); } }
Данный подход меняет привычное использование концепции ООП как способ описания объектов реального мира, на способ описания функции с ее аргументами, возвращаемым значением и зависимостями (сайд эффектами).
