Как показано на рис. 4.1, DeliveryDetailsPrinter передает задачу сортировки непосредственно объекту SorterByAddress. Если оставить структуру классов как есть, то впоследствии, если понадобится изменить функционал, могут возникнуть сложности. Предположим, что позже придется поменять порядок сортировки информации, чтобы она была упорядочена по имени отправителя. . Для этого нужно будет заменить объект SorterByAddress на другой, реализующий по-новому сформулированную обязанность. Но изменения также коснутся и объекта DeliveryDetailsPrinter, который также затронет выполнение задачи (рис. 4.2).
|
Как улучшить эту структуру? В случае изменения обязанностей одного объекта не хотелось бы затрагивать и другие объекты. . Такая структурная проблема возникает потому, что объект DeliveryDetailsPrinter определяет и то, что ему надо, и то, как именно ему это надо. Как уже говорилось, объект должен только сообщить, что ему нужно, но его не должно интересовать, как именно это будет реализовано. Разумеется, мы выполним задачу с помощью интерфейсов. На рис. 4.3 я изобразил интерфейс Sorter, который разделяет данные два объекта. Вместо того чтобы декларировать SorterByAddress, объект DeliveryDetailsPrinter только сообщает, что ему нужен Sorter. Теперь мы можем создать сколько угодно объектов, делающих то, что нужно DeliveryDetailsPrinter. Любой объект, реализующий интерфейс Sorter, может в любой момент удовлетворить зависимость объекта DeliveryDetailsPrinter.
|
На рис. 4.3 представлена наглядная иллюстрация зависимости между объектами DeliveryDetailsPrinter и SorterByAddress после того, как они были разделены посредством интерфейса.
Определение интерфейса Sorter представлено в следующем фрагменте кода:
public interface Sorter { void sortDetails(); }
|
Посмотрите на рис. 4.4 и сравните его с рис. 4.2. Поскольку объект DeliveryDetailsPrinter зависит не от реализации, а от интерфейса, нам больше не нужно изменять этот объект, если мы захотим модифицировать способ сортировки сведений о доставке.
После теоретического вступления вы понимаете, зачем нужны интерфейсы для разделения объектов, зависящих друг от друга согласно структуре классов. Теперь мы реализуем требование нашей задачи. Мы построим решение на чистом Java, без фреймворка, и обратим внимание на обязанности объектов и на разделение объектов посредством интерфейсов. В конце раздела мы создадим проект, в котором определим несколько объектов, взаимодействующих между собой для реализации данного сценария.
В разделе 4.2 мы изменим проект, добавив в него Spring, который будет управлять объектами и связями между ними посредством внедрения зависимостей. Благодаря такому поэтапному подходу вам будет легче заметить, что необходимо изменить в коде, чтобы подключить Spring к приложению, а также какие преимущества такое подключение дает.
|
4.1.2. Условия задачи
До сих пор мы рассматривали несложные примеры и использовали простые объекты (такие как Parrot). . Они весьма далеки от реальных промышленных приложений, зато помогают сконцентрироваться на изучаемых в данный момент синтаксических конструкциях. Пора пойти дальше и использовать то, что мы узнали в предыдущих главах, на примере, более близком к реальным условиям.
Предположим, нам нужно создать приложение, с помощью которого команда работников управляет своими обязанностями. Одна из функций продукта — возможность оставлять заметки к задачам. . Когда пользователи публикуют комментарий, он где-то сохраняется (например, в базе данных) — и приложение отправляет письмо по электронному адресу, указанному в конфигурации приложения.
Необходимо разработать объекты, правильно распределить между ними обязанности и создать абстракции для реализации этой функции.
|
4.1.3. Реализация сценариев использования без применения фреймворка
Сконцентрируемся на решении задачи, описанной в подразделе 4.1.1, используя то, что мы уже знаем об интерфейсах. Прежде всего нам нужно определить объекты (и обязанности), которые будут реализованы.
На практике в типичных приложениях объекты, выполняющие условия задачи, обычно называют сервисами — и мы тоже воспользуемся данным термином. . Нам нужен сервис, который реализует сценарий использования «напечатать комментарий». Назовем этот сервис CommentService. Я предпочитаю давать классам сервисов имена, заканчивающиеся на service, чтобы подчеркнуть их роль в проекте. (Подробнее о рекомендациях по выбору имен советую почитать главу 2 книги Роберта Мартина «Чистый код. Создание, анализ и рефакторинг» (Питер, 2021).)
Снова проанализировав требования системы, мы обнаружим, что данный сценарий использования состоит из двух частей: сохранения комментария и его отправки по электронной почте. Поскольку это совершенно разные действия, лучше разделить их на две обязанности, соответственно, нам понадобится реализовать два разных объекта.
Объект, который непосредственно взаимодействует с базой данных, обычно называют репозиторием. Встречается также термин «объект доступа к данным» (data access object, DAO). Поэтому первому объекту, реализующему обязанность сохранения комментария, мы дадим имя CommentRepository.
В реальных приложениях объекты, коммуницирующие с чем-то, что находится за пределами приложения, принято называть прокси. . Поэтому второй объект, обязанностью которого является отправка электронных писем, мы назовем CommentNotificationProxy. Связи между нашими объектами показаны на рис. 4.5.
|
Но постойте! Ведь только недавно было сказано, что не следует допускать прямых связей между реализациями! Необходимо разделить эти реализации посредством интерфейсов. Например, сейчас CommentRepository, очевидно, будет сохранять комментарии в базе данных. Но в будущем, возможно, его понадобится заменить на какую-нибудь другую технологию или внешний сервис. То же самое можно сказать и о CommentNotificationProxy. Сейчас он отправляет сообщения по электронной почте, но в следующих версиях уведомления могут пойти по какому-нибудь другому каналу. Нам обязательно нужно разделить CommentService и реализации его зависимостей, чтобы затем, когда эти зависимости потребуется изменить, не пришлось затронуть и объект, их использующий.
На рис. 4.6 показано, как разделить структуру этого класса посредством абстракций. Мы сделаем CommentRepository и CommentNotificationProxy не классами, а интерфейсами, которые затем реализуем, чтобы определить их функционал.
|
|
|
Комментарии: 0
Пока нет комментариев