По мере роста программных проектов становится все более важным поддерживать свой код организованным, поддерживаемым и масштабируемым. Именно здесь в игру вступают паттерны проектирования. Паттерны проектирования предоставляют проверенные, многократно используемые решения для общих проблем проектирования программного обеспечения, делая ваш код более эффективным и легким в управлении.
В этом руководстве мы подробно рассмотрим некоторые из самых популярных паттернов проектирования и покажем вам, как их реализовать в Spring Boot. К концу вы не только поймете эти паттерны концептуально, но и сможете применить их в своих собственных проектах с уверенностью.
Содержание
Введение в шаблоны проектирования
Шаблоны проектирования – это многократно используемые решения общих проблем проектирования программного обеспечения. Можно представлять их как bewe bewe bewe bewe лучшие практики, собранные в шаблоны, которые можно применять для решения конкретных задач в вашем коде. Они не привязаны к конкретному языку, но могут быть особенно мощными в Java благодаря ее объектно-ориентированной природе.
В этом руководстве мы рассмотрим:
-
Паттерн Singleton: Гарантирование наличия только одного экземпляра класса.
-
Паттерн Фабрика: Создание объектов без указания точного класса.
-
Паттерн Стратегия: Позволяет выбирать алгоритмы во время выполнения.
-
Паттерн Наблюдатель: Установление отношения издатель-подписчик.
Мы не только рассмотрим, как работают эти паттерны, но и исследуем, как их можно применить в Spring Boot для прикладных приложений.
Как настроить проект Spring Boot
Прежде чем мы погрузимся в паттерны, давайте настроим проект Spring Boot:
Предварительные требования
Убедитесь, что у вас есть:
-
Java 11+
-
Maven
-
Spring Boot CLI (optional)
-
Postman или curl (для тестирования)
Инициализация проекта
Вы можете быстро создать проект Spring Boot, используя Spring Initializr:
curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d name=DesignPatternsDemo \
-d javaVersion=11 -o design-patterns-demo.zip
unzip design-patterns-demo.zip
cd design-patterns-demo
Что такое Паттерн Одиночка?
Паттерн Одиночка гарантирует, что у класса есть только один экземпляр и предоставляет глобальную точку доступа к нему. Этот паттерн часто используется для служб, таких как ведение журнала, управление конфигурацией или подключения к базе данных.
Как реализовать паттерн Одиночка в Spring Boot
Бины Spring Boot по умолчанию являются синглтонами, что означает, что Spring автоматически управляет жизненным циклом этих бинов, чтобы гарантировать существование только одного экземпляра. Однако важно понимать, как работает паттерн Одиночка “под капотом”, особенно когда вы не используете управляемые Spring бины или вам нужно больше контроля над управлением экземпляров.
Давайте пройдем через ручную реализацию паттерна Одиночка, чтобы продемонстрировать, как можно контролировать создание одного экземпляра в вашем приложении.
Шаг 1: Создать класс LoggerService
В этом примере мы создадим простую службу ведения журнала, используя паттерн Одиночка. Цель – обеспечить использование одного и того же экземпляра ведения журнала всеми частями приложения.
public class LoggerService {
// Переменная для хранения единственного экземпляра
private static LoggerService instance;
// Приватный конструктор для предотвращения создания экземпляра извне
private LoggerService() {
// Этот конструктор специально пустой, чтобы предотвратить создание экземпляров другими классами
}
// Публичный метод для доступа к единственному экземпляру
public static synchronized LoggerService getInstance() {
if (instance == null) {
instance = new LoggerService();
}
return instance;
}
// Пример метода логирования
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
-
Статическая переменная (
instance
): Здесь хранится единственный экземплярLoggerService
. -
Приватный конструктор: Конструктор помечен как приватный, чтобы предотвратить создание новых экземпляров другими классами.
-
Синхронизированный метод
getInstance()
: Метод синхронизирован для обеспечения потокобезопасности, гарантируя создание только одного экземпляра, даже если несколько потоков пытаются получить доступ к нему одновременно. -
Ленивая инициализация: Экземпляр создается только при первом запросе (
ленивая инициализация
), что эффективно с точки зрения использования памяти.
Применение в реальном мире: Этот шаблон часто используется для общих ресурсов, таких как ведение журнала, настройки конфигурации или управление соединениями с базой данных, где требуется контроль доступа и обеспечение использования только одного экземпляра на протяжении всего приложения.
Шаг 2: Использование Singleton в контроллере Spring Boot
Теперь давайте посмотрим, как мы можем использовать наш одиночный LoggerService
в контроллере Spring Boot. Этот контроллер будет предоставлять конечную точку, которая регистрирует сообщение при каждом доступе к ней.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogController {
@GetMapping("/log")
public ResponseEntity<String> logMessage() {
// Доступ к единственному экземпляру LoggerService
LoggerService logger = LoggerService.getInstance();
logger.log("This is a log message!");
return ResponseEntity.ok("Message logged successfully");
}
}
-
Конечная точка GET: Мы создали конечную точку
/log
, которая при доступе регистрирует сообщение с использованиемLoggerService
. -
Использование Singleton: Вместо создания нового экземпляра
LoggerService
мы вызываемgetInstance()
, чтобы гарантировать использование одного и того же экземпляра каждый раз. -
Ответ: После записи журнала конечная точка возвращает ответ, указывающий на успешное выполнение.
Шаг 3: Тестирование паттерна Singleton
Теперь давайте протестируем эту конечную точку с помощью Postman или вашего браузера:
GET http://localhost:8080/log
Ожидаемый вывод:
-
Вывод в консоли:
[LOG] Это сообщение журнала!
-
HTTP-ответ:
Сообщение успешно зарегистрировано
Вы можете вызывать конечную точку несколько раз, и вы увидите, что используется один и тот же экземпляр LoggerService
, что подтверждается постоянным выводом журнала.
Реальные примеры использования паттерна Singleton
Вот когда вам может понадобиться использовать паттерн Singleton в приложениях реального мира:
-
Управление конфигурацией: Убедитесь, что ваше приложение использует согласованный набор параметров конфигурации, особенно когда эти параметры загружаются из файлов или баз данных.
-
Пулы подключений к базе данных: Управляют доступом к ограниченному количеству подключений к базе данных, обеспечивая совместное использование одного и того же пула в приложении.
-
Кэширование: Поддерживают единственный экземпляр кэша для избежания несогласованных данных.
-
Службы регистрации: Как показано в этом примере, используйте единственную службу регистрации для централизации вывода журнала по различным модулям вашего приложения.
Основные моменты
-
Шаблон Singleton – простой способ гарантировать создание только одного экземпляра класса.
-
Обеспечение потокобезопасности критично, если к Singleton обращаются несколько потоков, поэтому мы использовали
synchronized
в нашем примере. -
Бины Spring Boot уже по умолчанию являются синглтонами, но понимание того, как реализовать их вручную, помогает вам получить больший контроль, когда это необходимо.
Здесь рассматривается реализация и использование паттерна Singleton. Далее мы рассмотрим паттерн Фабрика, чтобы увидеть, как он может помочь упростить создание объектов.
Что такое паттерн Фабрика?
Паттерн Фабрика позволяет создавать объекты без указания конкретного класса. Этот паттерн полезен, когда у вас есть различные типы объектов, которые нужно создавать на основе некоторого ввода.
Как реализовать Фабрику в Spring Boot
Паттерн Фабрика чрезвычайно полезен, когда вам нужно создавать объекты на основе определенных критериев, но вы хотите отделить процесс создания объекта от логики вашего основного приложения.
В этом разделе мы рассмотрим создание NotificationFactory
для отправки уведомлений по электронной почте или SMS. Это особенно полезно, если вы планируете добавить больше типов уведомлений в будущем, таких как уведомления о пуш-уведомлениях или встроенные предупреждения, без изменения вашего существующего кода.
Шаг 1: Создание интерфейса Notification
Первый шаг – определить общий интерфейс, который будут реализовывать все типы уведомлений. Это гарантирует, что каждый тип уведомления (Email, SMS и т. д.) будет иметь согласованный метод send()
.
public interface Notification {
void send(String message);
}
-
Цель: Интерфейс
Notification
определяет контракт для отправки уведомлений. Любой класс, реализующий этот интерфейс, должен предоставить реализацию методаsend()
. -
Масштабируемость: Используя интерфейс, вы можете легко расширить ваше приложение в будущем, чтобы включить другие типы уведомлений без изменения существующего кода.
Шаг 2: Реализуйте EmailNotification
и SMSNotification
Теперь давайте реализуем два конкретных класса, один для отправки электронных писем, а другой для отправки SMS-сообщений.
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email: " + message);
}
}
public class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
Шаг 3: Создайте NotificationFactory
Класс NotificationFactory
отвечает за создание экземпляров Notification
на основе указанного типа. Этот дизайн гарантирует, что NotificationController
не должен знать детали создания объектов.
public class NotificationFactory {
public static Notification createNotification(String type) {
switch (type.toUpperCase()) {
case "EMAIL":
return new EmailNotification();
case "SMS":
return new SMSNotification();
default:
throw new IllegalArgumentException("Unknown notification type: " + type);
}
}
}
Метод Фабрики (createNotification()
):
-
Метод фабрики принимает строку (
type
) в качестве входных данных и возвращает экземпляр соответствующего класса уведомлений. -
Оператор выбора Switch: Оператор выбора switch выбирает соответствующий тип уведомления на основе входных данных.
-
Обработка ошибок: Если предоставленный тип не распознается, генерируется исключение
IllegalArgumentException
. Это обеспечивает раннее обнаружение недопустимых типов.
Почему использовать фабрику?
-
Отвязка: Шаблон фабрики отвязывает создание объекта от бизнес-логики. Это делает ваш код более модульным и легким в обслуживании.
-
Расширяемость: Если вы хотите добавить новый тип уведомления, вам нужно лишь обновить фабрику, не затрагивая логику контроллера.
Шаг 4: Использование фабрики в контроллере Spring Boot
Теперь давайте объединим все это, создав контроллер Spring Boot, который использует NotificationFactory
для отправки уведомлений на основе запроса пользователя.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotificationController {
@GetMapping("/notify")
public ResponseEntity<String> notify(@RequestParam String type, @RequestParam String message) {
try {
// Создаем соответствующий объект уведомления с использованием фабрики
Notification notification = NotificationFactory.createNotification(type);
notification.send(message);
return ResponseEntity.ok("Notification sent successfully!");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
Конечная точка GET (/notify
):
-
Контроллер открывает конечную точку
/notify
, которая принимает два параметра запроса:type
(либо “EMAIL”, либо “SMS”) иmessage
. -
Он использует
NotificationFactory
для создания соответствующего типа уведомления и отправляет сообщение. -
Обработка ошибок: Если предоставлен недопустимый тип уведомления, контроллер перехватывает
IllegalArgumentException
и возвращает ответ400 Bad Request
.
Шаг 5: Тестирование шаблона Factory
Давайте протестируем конечную точку с помощью Postman или браузера:
-
Отправка уведомления по электронной почте:
GET http://localhost:8080/notify?type=email&message=Hello%20Email
Результат:
Отправка Email: Hello Email
-
Отправка уведомления по SMS:
GET http://localhost:8080/notify?type=sms&message=Hello%20SMS
Вывод:
Отправка SMS: Hello SMS
-
Тест с недопустимым типом:
GET http://localhost:8080/notify?type=unknown&message=Test
Вывод:
Неверный запрос: Неизвестный тип уведомления: unknown
Практические примеры использования паттерна Фабрика в реальном мире
Паттерн Фабрика особенно полезен в ситуациях, где:
-
Динамическое создание объектов: Когда вам нужно создавать объекты на основе пользовательского ввода, например, отправка различных типов уведомлений, генерация отчетов в различных форматах или обработка различных способов оплаты.
-
Разделение создания объектов: Используя фабрику, вы можете отделить основную бизнес-логику от создания объектов, что делает ваш код более поддерживаемым.
-
Масштабируемость: Легко расширить ваше приложение для поддержки новых типов уведомлений без изменения существующего кода. Просто добавьте новый класс, реализующий интерфейс
Notification
, и обновите фабрику.
Что такое паттерн Стратегия?
Паттерн Стратегия отлично подходит, когда вам нужно динамически переключаться между несколькими алгоритмами или поведениями. Он позволяет определить семейство алгоритмов, инкапсулировать каждый из них в отдельные классы и легко менять их во время выполнения. Это особенно полезно для выбора алгоритма на основе конкретных условий, что делает ваш код чистым, модульным и гибким.
Применение в реальном мире: Представьте себе систему электронной коммерции, которая должна поддерживать несколько вариантов оплаты, такие как кредитные карты, PayPal или банковские переводы. Используя паттерн Стратегия, вы можете легко добавлять или изменять методы оплаты без изменения существующего кода. Такой подход гарантирует, что ваше приложение остается масштабируемым и обслуживаемым при внедрении новых функций или обновлении существующих.
Мы продемонстрируем этот паттерн на примере Spring Boot, который обрабатывает платежи с использованием стратегии оплаты кредитной картой или PayPal.
Шаг 1: Определите интерфейс PaymentStrategy
Начнем с создания общего интерфейса, который будут реализовывать все стратегии оплаты:
public interface PaymentStrategy {
void pay(double amount);
}
Интерфейс определяет контракт для всех методов оплаты, обеспечивая согласованность в реализации.
Шаг 2: Реализуйте стратегии оплаты
Создайте конкретные классы для оплаты кредитной картой и PayPal.
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via PayPal");
}
}
Каждый класс реализует метод pay()
с собственным поведением.
Шаг 3: Используйте стратегию в контроллере
Создайте контроллер для динамического выбора стратегии оплаты на основе ввода пользователя:
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping("/pay")
public ResponseEntity<String> processPayment(@RequestParam String method, @RequestParam double amount) {
PaymentStrategy strategy = selectPaymentStrategy(method);
if (strategy == null) {
return ResponseEntity.badRequest().body("Invalid payment method");
}
strategy.pay(amount);
return ResponseEntity.ok("Payment processed successfully!");
}
private PaymentStrategy selectPaymentStrategy(String method) {
switch (method.toUpperCase()) {
case "CREDIT": return new CreditCardPayment();
case "PAYPAL": return new PayPalPayment();
default: return null;
}
}
}
Конечная точка принимает method
и amount
в качестве параметров запроса и обрабатывает платеж с использованием соответствующей стратегии.
Шаг 4: Тестирование конечной точки
-
Оплата кредитной картой:
GET http://localhost:8080/pay?method=credit&amount=100
Вывод:
Оплачено $100.0 кредитной картой
-
Оплата через PayPal:
GET http://localhost:8080/pay?method=paypal&amount=50
Вывод:
Оплачено $50.0 через PayPal
-
Недопустимый метод:
GET http://localhost:8080/pay?method=bitcoin&amount=25
Вывод:
Недействительный метод оплаты
Сценарии использования шаблона стратегии
-
Обработка платежей: Динамическое переключение между различными платежными шлюзами.
-
Алгоритмы сортировки: Выбор лучшего метода сортировки на основе размера данных.
-
Экспорт файлов: Экспорт отчетов в различных форматах (PDF, Excel, CSV).
Основные моменты
-
Шаблон Стратегия сохраняет ваш код модульным и следует принципу “Открыто/Закрыто”.
-
Добавление новых стратегий легко — просто создайте новый класс, реализующий интерфейс
PaymentStrategy
. -
Это идеально для сценариев, где нужен гибкий выбор алгоритма во время выполнения.
Далее мы рассмотрим паттерн Наблюдатель, идеально подходящий для обработки событийно-ориентированных архитектур.
Что такое паттерн Наблюдатель?
Шаблон Наблюдатель идеально подходит, когда у вас есть один объект (субъект), который должен уведомлять несколько других объектов (наблюдателей) об изменениях в своем состоянии. Он прекрасно подходит для событийно-ориентированных систем, где обновления необходимо отправлять различным компонентам без создания тесной связи между ними. Этот шаблон позволяет поддерживать чистую архитектуру, особенно когда разные части вашей системы должны реагировать на изменения независимо.
Реальный случай использования: Этот шаблон часто используется в системах, которые отправляют уведомления или алерты, таких как приложения для чата или трекеры цен акций, где обновления необходимо отправлять пользователям в реальном времени. Используя шаблон Наблюдатель, вы можете легко добавлять или удалять типы уведомлений, не изменяя основную логику.
Мы продемонстрируем, как реализовать этот шаблон в Spring Boot, создав простую систему уведомлений, где как Email, так и SMS уведомления отправляются каждый раз, когда пользователь регистрируется.
Шаг 1: Создайте интерфейс Observer
Мы начинаем с определения общего интерфейса, который все наблюдатели должны реализовать:
public interface Observer {
void update(String event);
}
Интерфейс устанавливает контракт, согласно которому все наблюдатели должны реализовать метод update()
, который будет вызываться всякий раз, когда субъект изменяется.
Шаг 2: Реализуйте EmailObserver
и SMSObserver
Далее мы создаем две конкретные реализации интерфейса Observer
, чтобы обрабатывать уведомления по электронной почте и SMS.
EmailObserver
Класс
public class EmailObserver implements Observer {
@Override
public void update(String event) {
System.out.println("Email sent for event: " + event);
}
}
Класс EmailObserver
отвечает за отправку уведомлений по электронной почте каждый раз, когда он уведомляется о событии.
Класс SMSObserver
public class SMSObserver implements Observer {
@Override
public void update(String event) {
System.out.println("SMS sent for event: " + event);
}
}
Класс SMSObserver
отвечает за отправку SMS-уведомлений всякий раз, когда он уведомляется.
Шаг 3: Создание класса UserService
(Подписчик)
Теперь мы создадим класс UserService
, который действует как предмет, уведомляя своих зарегистрированных наблюдателей всякий раз, когда пользователь регистрируется.
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
private List<Observer> observers = new ArrayList<>();
// Метод для регистрации наблюдателей
public void registerObserver(Observer observer) {
observers.add(observer);
}
// Метод для уведомления всех зарегистрированных наблюдателей о событии
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
// Метод для регистрации нового пользователя и уведомления наблюдателей
public void registerUser(String username) {
System.out.println("User registered: " + username);
notifyObservers("User Registration");
}
}
-
Список наблюдателей: Отслеживает всех зарегистрированных наблюдателей.
-
registerObserver()
Метод: Добавляет новых наблюдателей в список. -
notifyObservers()
Метод: Уведомляет всех зарегистрированных наблюдателей, когда происходит событие. -
registerUser()
Метод: Регистрирует нового пользователя и запускает уведомления всем наблюдателям.
Шаг 4: Использование паттерна Наблюдатель в контроллере
Наконец, мы создадим контроллер Spring Boot для размещения конечной точки регистрации пользователя. Этот контроллер зарегистрирует как EmailObserver
, так и SMSObserver
в UserService
.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController() {
this.userService = new UserService();
// Регистрация наблюдателей
userService.registerObserver(new EmailObserver());
userService.registerObserver(new SMSObserver());
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestParam String username) {
userService.registerUser(username);
return ResponseEntity.ok("User registered and notifications sent!");
}
}
-
Конечная точка (
/register
): Принимает параметрusername
и регистрирует пользователя, запуская уведомления всем наблюдателям. -
Наблюдатели: И
EmailObserver
, иSMSObserver
зарегистрированы вUserService
, поэтому они уведомляются при регистрации пользователя.
Тестирование шаблона Наблюдатель
Теперь давайте протестируем нашу реализацию с помощью Postman или браузера:
POST http://localhost:8080/api/register?username=JohnDoe
Ожидаемый вывод в консоли:
User registered: JohnDoe
Email sent for event: User Registration
SMS sent for event: User Registration
Система регистрирует пользователя и уведомляет как Email, так и SMS наблюдателей, демонстрируя гибкость шаблона Наблюдатель.
Прикладные области шаблона Наблюдатель в реальном мире
-
Системы уведомлений: Отправка обновлений пользователям через различные каналы (электронная почта, SMS, push-уведомления) при возникновении определенных событий.
-
Архитектуры, основанные на событиях: Уведомление нескольких подсистем при наступлении определенных действий, таких как действия пользователей или системные оповещения.
-
Потоковая передача данных: Трансляция изменений данных различным потребителям в реальном времени (например, актуальные цены на акции или ленты социальных медиа).
Как использовать механизм внедрения зависимостей Spring Boot
До этого мы вручную создавали объекты для демонстрации шаблонов проектирования. Однако в реальных приложениях на Spring Boot предпочтительным способом управления созданием объектов является внедрение зависимостей (DI). DI позволяет Spring автоматически управлять созданием и связыванием ваших классов, делая ваш код более модульным, тестируемым и поддерживаемым.
Давайте переработаем наш пример шаблона Стратегия, чтобы воспользоваться мощными возможностями внедрения зависимостей Spring Boot. Это позволит нам динамически переключаться между стратегиями оплаты, используя аннотации Spring для управления зависимостями.
Обновленный шаблон Стратегия с использованием внедрения зависимостей Spring Boot.
В нашем преобразованном примере мы воспользуемся аннотациями Spring, такими как @Component
, @Service
и @Autowired
, чтобы упростить процесс внедрения зависимостей.
Шаг 1: Аннотировать стратегии оплаты с помощью @Component
Сначала мы отметим наши реализации стратегий аннотацией @Component
, чтобы Spring мог автоматически обнаруживать и управлять ими.
@Component("creditCardPayment")
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
@Component("payPalPayment")
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal");
}
}
-
@Component
Аннотация: Добавляя@Component
, мы сообщаем Spring, что эти классы должны рассматриваться как управляемые бины Spring. Строковое значение ("creditCardPayment"
и"payPalPayment"
) выступает в качестве идентификатора бина. -
Гибкость: Эта настройка позволяет нам переключаться между стратегиями, используя соответствующий идентификатор бина.
Шаг 2: Переработать PaymentService
для использования внедрения зависимостей
Затем давайте изменить PaymentService
, чтобы внедрить конкретную стратегию оплаты с помощью @Autowired
и @Qualifier
.
@Service
public class PaymentService {
private final PaymentStrategy paymentStrategy;
@Autowired
public PaymentService(@Qualifier("payPalPayment") PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
@Service
Аннотация: ПомечаетPaymentService
как управляемый сервисный бин Spring.-
@Autowired
: Spring автоматически внедряет необходимую зависимость. -
@Qualifier
: Указывает, какую реализациюPaymentStrategy
внедрить. В этом примере мы используем"payPalPayment"
. -
Простота настройки: Просто изменив значение
@Qualifier
, вы можете переключить стратегию оплаты без изменения какой-либо бизнес-логики.
Шаг 3: Использование отрефакторенной службы в контроллере
Чтобы увидеть преимущества этого рефакторинга, давайте обновим контроллер для использования нашего PaymentService
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class PaymentController {
private final PaymentService paymentService;
@Autowired
public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}
@GetMapping("/pay")
public String makePayment(@RequestParam double amount) {
paymentService.processPayment(amount);
return "Payment processed using the current strategy!";
}
}
-
@Autowired
: Контроллер автоматически получаетPaymentService
с внедренной стратегией оплаты. -
GET-конечная точка (
/pay
): При доступе к ней обрабатывается платеж с использованием в настоящее время настроенной стратегии (PayPal в этом примере).
Тестирование переработанного шаблона стратегии с DI
Теперь давайте протестируем новую реализацию с помощью Postman или браузера:
GET http://localhost:8080/api/pay?amount=100
Ожидаемый вывод:
Paid $100.0 using PayPal
Если вы измените квалификатор в PaymentService
на "creditCardPayment"
, вывод изменится соответственно:
Paid $100.0 with Credit Card
Преимущества использования инъекции зависимостей
-
Слабая связанность: Сервису и контроллеру не нужно знать детали обработки платежа. Они просто полагаются на Spring для внедрения правильной реализации.
-
Модульность: Вы можете легко добавить новые методы оплаты (например,
BankTransferPayment
,CryptoPayment
), создав новые классы с аннотациями@Component
и настраивая@Qualifier
. - Настройка: Используя профили Spring, вы можете переключать стратегии в зависимости от окружения (например, разработка против производства).
Пример: Вы можете использовать @Profile
для автоматической инъекции различных стратегий на основе активного профиля:
@Component
@Profile("dev")
public class DevPaymentStrategy implements PaymentStrategy { /* ... */ }
@Component
@Profile("prod")
public class ProdPaymentStrategy implements PaymentStrategy { /* ... */ }
Основные моменты
-
Используя DI в Spring Boot, вы можете упростить создание объектов и улучшить гибкость вашего кода.
-
Шаблон Стратегия в сочетании с DI позволяет легко переключаться между различными стратегиями без изменения основной бизнес-логики.
-
Используя
@Qualifier
и профили Spring, вы получаете гибкость настройки вашего приложения в зависимости от различных сред или требований.
Этот подход не только делает ваш код более чистым, но также готовит его к более сложным конфигурациям и масштабированию в будущем. В следующем разделе мы рассмотрим лучшие практики и советы по оптимизации для повышения производительности ваших приложений на Spring Boot.
Лучшие практики и советы по оптимизации
Общие лучшие практики
-
Не злоупотребляйте паттернами: используйте их только при необходимости. Чрезмерная инженерия может усложнить поддержку вашего кода.
-
Предпочитайте композицию наследованию: такие паттерны, как Стратегия и Наблюдатель, являются отличными примерами этого принципа.
-
Держите ваши паттерны гибкими: используйте интерфейсы, чтобы ваш код оставался независимым.
Соображения производительности
-
Паттерн Singleton: обеспечьте безопасность потоков, используя
synchronized
илиДизайн Singleton Билла Пью
. -
Паттерн Фабрика: кэшируйте объекты, если их создание дорогостоящее.
-
Паттерн Наблюдатель: используйте асинхронную обработку, если у вас много наблюдателей, чтобы предотвратить блокировку.
Расширенные темы
-
Использование Рефлексии с паттерном Фабрика для динамической загрузки классов.
-
Использование Профилей Spring для переключения стратегий в зависимости от окружения.
-
Добавление Документации Swagger для ваших конечных точек API.
Заключение и ключевые моменты
В этом руководстве мы рассмотрели некоторые из самых мощных шаблонов проектирования — Одиночка, Фабрика, Стратегия и Наблюдатель — и продемонстрировали, как их реализовать в Spring Boot. Давайте кратко изложим каждый шаблон и выделим, для чего он лучше всего подходит:
Шаблон Одиночка:
-
Итог: Гарантирует, что у класса есть только один экземпляр и обеспечивает глобальную точку доступа к нему.
-
Лучше всего подходит для: Управления общими ресурсами, такими как настройки конфигурации, подключения к базе данных или службы регистрации. Идеально подходит, когда вы хотите контролировать доступ к общему экземпляру по всему вашему приложению.
Шаблон Фабрика:
-
Обзор: Предоставляет способ создания объектов без указания точного класса для инстанцирования. Этот шаблон отделяет создание объекта от бизнес-логики.
-
Лучше всего подходит для: Сценариев, где необходимо создавать различные типы объектов на основе входных условий, например, отправка уведомлений по электронной почте, SMS или push-уведомления. Это отлично подходит для сделать ваш код более модульным и расширяемым.
Шаблон Стратегия:
-
Обзор: Позволяет определить семейство алгоритмов, инкапсулировать каждый из них и делать их взаимозаменяемыми. Этот шаблон помогает выбирать алгоритм во время выполнения.
-
Лучше всего подходит для: Когда вам нужно переключаться между различными поведениями или алгоритмами динамически, например, обработка различных способов оплаты в приложении электронной коммерции. Это делает ваш код более гибким и соответствует принципу открытости/закрытости.
Паттерн Наблюдатель:
-
Сводка: Определяет зависимость “один ко многим” между объектами, так что при изменении состояния одного объекта все его зависимые объекты уведомляются автоматически.
-
Лучше всего для: Систем, основанных на событиях, таких как сервисы уведомлений, обновления в реальном времени в приложениях чата или системы, которые должны реагировать на изменения в данных. Идеально подходит для разделения компонентов и улучшения масштабируемости вашей системы.
Что дальше?
Теперь, когда вы изучили эти важные шаблоны проектирования, попробуйте интегрировать их в ваши существующие проекты, чтобы увидеть, как они могут улучшить структуру вашего кода и масштабируемость. Вот несколько предложений для дальнейшего изучения:
-
Экспериментируйте: Попробуйте реализовать другие шаблоны проектирования, такие как Декоратор, Заместитель и Строитель, чтобы расширить ваш набор инструментов.
-
Практика: Используйте эти шаблоны для рефакторинга существующих проектов и улучшения их поддерживаемости.
-
Поделиться: Если у вас есть вопросы или вы хотите поделиться своим опытом, не стесняйтесь обращаться!
Надеюсь, этот руководство помог вам понять, как эффективно использовать шаблоны проектирования на Java. Продолжайте экспериментировать и счастливого кодирования!
Source:
https://www.freecodecamp.org/news/how-to-use-design-patterns-in-java-with-spring-boot/