Я помню, когда пытался получить свою первую работу в качестве разработчика Java, и мой брат помогал мне освоить основы ООП, выполняя некоторые обычные упражнения по программированию. Мне повезло, я успешно прошел собеседование в ведущей компании (не раньше, чем провалил дюжину собеседований).

Сфера деятельности, которую я делал, была очень узкой. Я писал код, тестировал локально, иногда развертывал в среде разработки с помощью Jenkins и снова тестировал там. Тогда бума CI/CD еще не было. Микросервисы еще не были в моде. Каждый инженер сосредоточился на «изучении Java», поэтому в основном мы занимались кодированием.

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

В этих основах кодирования мы нашли освоение столпов ООП.

Кодирование — это всего лишь инструмент для решения проблемы, и мы, как инженеры, должны знать, когда и как его применять. Но это по-прежнему первое, чему мы учимся, и я считаю, что это оказывает большое влияние на наши навыки решения проблем, которые необходимы в сложной среде, с которой мы сталкиваемся в наши дни.

Но как столпы ООП влияют на дизайн системы и знание продукта? Давайте рассмотрим это вместе!

Столпы ООП

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

Возьмем пример: у меня есть банковское приложение, в котором я делаю денежный перевод, и он отображается в ленте переводов.

Я буду достаточно ленив, чтобы позволить Chat-GPT создать определения, которые, по моему мнению, достаточно хороши для целей статьи. Если вы их не знаете, я бы рекомендовал не только искать более полные определения, но и выполнять упражнения по их применению.

Инкапсуляция и абстракция

Chat-GPT. Под инкапсуляцией понимается объединение данных и связанных поведений (методов) в единый блок, называемый объектом. Он защищает целостность данных, контролируя доступ с помощью методов, и позволяет рассматривать объекты как автономные объекты со своим собственным состоянием и поведением.

Chat-GPT: Абстракция фокусируется на создании упрощенных и обобщенных моделей реальных концепций в виде абстрактных классов и интерфейсов. Абстрактные классы определяют общие характеристики и поведение, которые могут наследовать и расширять подклассы. Интерфейсы определяют контракт методов, которым должны следовать реализующие классы. Абстракция помогает управлять сложностью, продвигать модульный дизайн и скрывать детали реализации.

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

Это говорит нам о чем-то важном: представление (абстракция) и поведение (инкапсуляция) передачи будут разными в каждой области.

Появляется новое требование: описание перевода, которое вводит клиент, должно быть переведено на язык, который клиент выбрал в настройках. Куда относится изменение?

  1. В сервисе переводов: мы можем перевести его и хранить как оригинальные, так и переведенные тексты. Любой, кто заинтересован в любом из текстов, может получить информацию оттуда вместе со всеми деталями перевода.
  2. В службе каналов: когда мы получаем информацию о переводе, мы переводим ее и сохраняем для отображения в ленте (предположим, клиент не может изменить язык в своих настройках… какой ужасный продукт).

Обе реализации будут работать, в этом нет сомнений. Но будет ли это новое поведение перевода соответствовать контексту службы передачи?Должна ли абстракция передачи инкапсулировать это поведение? . Мы ожидаем, что служба переводов позаботится о фактическом выполнении перевода, проверив соответствие и переместив деньги в бухгалтерскую книгу. Его представление инкапсулирует поведение, которое не имеет ничего общего с показом перевода клиенту.

Но что, если есть другой сервис, которому нужен переведенный текст, не лучше ли просто сделать это в сервисе переводов, чтобы сделать это один раз? Ну, это цена, которую приходится платить за архитектуру распределенных систем, где каждый сервис имеет определенный контекст, обязанности и поведение (ну, на самом деле… лучше обсудить это в будущем, но это достаточно верно, когда мы говорим о домен). Инкапсуляция неправильного поведения в неправильную абстракцию — это способ гарантировать непоследовательное разделение задач.

Конечно, это тесно связано с принципами SOLID, но это совсем другой пост. Не забудьте просмотреть их здесь.

Для людей, занимающихся DDD, это хорошее напоминание о том, что не все, что связано с предметной областью, является концепцией DDD, это просто ООП.

Полиморфизм и Наследование

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

Chat-GPT: полиморфизм позволяет использовать объекты и рассматривать их как экземпляры разных классов в иерархии классов. Это позволяет объектам по-разному реагировать на один и тот же метод в зависимости от их конкретного типа класса. Полиморфизм повышает гибкость, расширяемость и удобство сопровождения кода, предоставляя универсальный и взаимозаменяемый способ работы с объектами.

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

Имеет ли смысл создавать для этого совершенно новый сервис/модуль? Кажется, что пока нет. В конце концов, оба являются внутренним переносом, хотя поведение может немного отличаться для каждого. Но мы инкапсулировали поведение, что означает, что он предоставляет контракт, чтобы что-то делать, а логика находится внутри объекта, он знает, как себя вести. Наш новый перевод также является внутренним переводом, поэтому контракт тот же, но поведение будет другим.

abstract class InternalTransfer {
  public abstract boolean isCompliant();
}
  
class ToADifferentUserTransfer extends InternalTransfer {
  @Override
  public boolean isCompliant(){
    ....
  }
}

class ToTheSameUserTransfer extends InternalTransfer {
  @Override
  public boolean isCompliant(){
    ....
  }
}

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

«Почему вы рассматривали это как другой тип перевода, если оба являются внутренним переводом? Кроме того, шаги по применению бизнес-правил такие же, но некоторые из них просто применяются по-другому».

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

Как мы видим, два типа передачи принадлежат одному и тому же домену, который можно рассматривать одинаково, поскольку они используют один и тот же контракт.

Но мы также должны знать, что два контракта, которые выглядят одинаково, не обязательно одинаковы. Это всегда будет сводиться к концепции продукта. Помните, что в этом сценарии два платежа, которые сегодня ведут себя одинаково, могут развиваться по-разному.

Выводы

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

  • Мы используем концепции ООП для моделирования нашего бизнеса, даже не задумываясь о коде.
  • Неспособность овладеть основами ООП связана с неудачей в проектных решениях, поскольку человек все еще пытается управлять концепциями, необходимыми в меньшем масштабе.
  • Новые инженеры должны уделять больше внимания обучению кодированию, а более опытные инженеры должны уделять больше внимания распространению знаний.
  • Столпы ООП являются частью нашего естественного языка для описания мира.
  • Люди, не являющиеся техническими специалистами, тоже используют эти концепции, даже если формально не относятся к ООП.

Отличный способ снова начать соприкасаться с нашими корнями — это просмотреть определения, найти примеры, выполнить ката, потратить время на изучение кода и систем нашего продукта. Спросить, почему вещи такие, какие они есть, — это отличный способ начать улучшать развивающиеся продукты.

Немного дополнительного материала для чтения!