Застосування шаблону Фабрики в Java Spring Boot: Посібник для розробників

Шаблон Фабрики є потужним інструментом для делегування логіки створення об'єктів підкласам або спеціальним фабричним класам. Його особливість полягає в тому, що він дозволяє розірвати зв'язок між створенням об'єкта та його використанням, що є корисним у багатьох реальних сценаріях розробки.

У своєму досвіді я часто застосовував шаблони Singleton та Factory, навіть не розуміючи їхньої повної суті. Працюючи над різними проектами, від малих інструментів до великих систем, я зрозумів, що ці шаблони дійсно допомагають організувати код і роблять його більш зручним для підтримки. Тому я вирішив більш детально дослідити їх і поділитися цим досвідом.

Проблема

Уявіть, що ви створюєте додаток для відправки різних типів сповіщень, таких як Email, SMS та Push. Потрібно мати можливість динамічно змінювати типи сповіщень в залежності від запиту або конфігурації. Проте хардкодинг логіки типу if (type.equals("email")) { ... } у всьому проекті призводить до кількох серйозних проблем:

  • Порушується принцип відкритості/закритості (Open/Closed Principle).
  • Складніше тестувати.
  • Кожен раз потрібно змінювати код, додаючи нові типи сповіщень.

Шаблон Фабрики допомагає уникнути цих проблем.

Що таке шаблон Фабрики?

Шаблон Фабрики дозволяє створювати об'єкти без розкриття логіки їх створення для викликаючого коду. Замість того, щоб безпосередньо викликати конструктор, ми звертаємося до фабрики, яка на основі певного параметра (наприклад, типу або ключа) створює потрібний об'єкт.

Це означає, що:

  • Клієнт не знає, який саме клас він отримує.
  • Нові реалізації можна додавати без зміни коду клієнта.
  • Логіка створення об'єктів централізована та абстрагована.

У Spring для реалізації фабрики можна використовувати впровадження залежностей (dependency injection) та інтерфейси (interfaces), що дозволяє створити гнучку та розширювану фабрику.

Покрокова інструкція: Створення фабрики сповіщень

  1. Спочатку визначаємо інтерфейс для сервісів сповіщень:

public interface NotificationService {
void send(String message);
}

  1. Далі створюємо кілька реалізацій цього інтерфейсу:

@Service
public class EmailNotificationService implements NotificationService {
public void send(String message) {
System.out.println("Sending EMAIL: " + message);
}
}

@Service
public class SmsNotificationService implements NotificationService {
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}

@Service
public class PushNotificationService implements NotificationService {
public void send(String message) {
System.out.println("Sending PUSH: " + message);
}
}

  1. Тепер створюємо саму фабрику:

@Component
public class NotificationFactory {

private final Map services;

public NotificationFactory(List serviceList) {
this.services = new HashMap<>();
for (NotificationService service : serviceList) {
String key = service.getClass().getSimpleName()
.replace("NotificationService", "")
.toLowerCase();
services.put(key, service);
}
}

public NotificationService getService(String type) {
NotificationService service = services.get(type.toLowerCase());
if (service == null) {
throw new IllegalArgumentException("Unsupported notification type: " + type);
}
return service;
}
}

Типова фабрика з If-Else або Switch (старий підхід)

До використання Spring та впровадження залежностей, ми часто реалізовували фабрику наступним чином:

public class SimpleNotificationFactory {

public NotificationService getService(String type) {
switch (type.toLowerCase()) {
case "email":
return new EmailNotificationService();
case "sms":
return new SmsNotificationService();
case "push":
return new PushNotificationService();
default:
throw new IllegalArgumentException("Unsupported notification type: " + type);
}
}
}

Чому це може бути проблемою:

  • Потрібно вручну керувати створенням об'єктів.
  • Порушує принцип відкритості/закритості (Open/Closed Principle).
  • Це не дуже зручно для тестування і масштабування.

Замість цього ми можемо використати фабрику на основі впровадження залежностей у Spring.

Як це працює

  • Spring Boot автоматично впроваджує всі біні (beans), що реалізують NotificationService, у конструктор фабрики.
  • Фабрика потім зв'язує ці біні за допомогою ключа, який утворюється з назви класу.
  • Для отримання потрібної реалізації достатньо викликати метод getService("email"), getService("sms") тощо.

Переваги:

  • ✅ Чистий і динамічний вибір сервісу.
  • ✅ Відповідає принципу відкритості/закритості.
  • ✅ Легко розширюється (достатньо додати новий @Service).
  • ✅ Легке тестування і підтримка.

Використання в REST Controller

@RestController
@RequestMapping("/notify")
public class NotificationController {

private final NotificationFactory factory;

public NotificationController(NotificationFactory factory) {
this.factory = factory;
}

@PostMapping
public ResponseEntity send(@RequestParam String type, @RequestBody String message) {
NotificationService service = factory.getService(type);
service.send(message);
return ResponseEntity.ok("Notification sent via " + type);
}
}

Висновок

Ця стаття ґрунтується на моєму практичному досвіді в реальних проектах. З часом я зрозумів, що використовував шаблон Фабрики та навіть Singleton, не розуміючи повністю їхнього проектування. Написання цього посту допомогло мені краще зрозуміти ці шаблони, і сподіваюся, що він допоможе й вам.

Шаблон Фабрики, особливо в поєднанні з впровадженням залежностей у Spring Boot, допомагає створювати чистий, розширюваний та підтримуваний код. Незалежно від того, чи працюєте ви над сервісами сповіщень, платіжними шлюзами чи чимось, що вимагає гнучкої зміни реалізацій, шаблон Фабрики завжди буде корисним.

“Ми часто використовуємо шаблони проектування неусвідомлено. Але як тільки ми по-справжньому розуміємо їх, ми використовуємо їх мудро.”

Чи використовували ви шаблон Фабрики у своїх проектах на Spring Boot? Поділіться своїм досвідом!

Перекладено з: Applying the Factory Pattern in Java Spring Boot: A Developer’s Guide