Очистіть свій Java код за допомогою патерну Abstract Factory
Припиніть дублювати створення об’єктів і розблокуйте масштабовані комбінації пов’язаних об’єктів, таких як Email + SMS сповіщення.
Проблема: Жорстка і повторювана логіка сповіщень
Припустимо, ви створюєте систему, яка надсилає різні типи сповіщень. З часом впроваджуються нові типи сповіщень, які поєднують різні канали:
- ALERT: Надсилає і Email, і SMS
- NEWSLETTER: Надсилає лише Email
- PROMOTION: Надсилає лише SMS
Спочатку ви можете написати щось подібне:
public class NotificationService { public void send(String type, String message) { if (type.equals("ALERT")) { Notification email = new EmailNotification(); Notification sms = new SMSNotification(); email.notifyUser(message); sms.notifyUser(message); } else if (type.equals("NEWSLETTER")) { Notification email = new EmailNotification(); email.notifyUser(message); } else if (type.equals("PROMOTION")) { Notification sms = new SMSNotification(); sms.notifyUser(message); } else { throw new IllegalArgumentException("Unknown type: " + type); } } public static void main(String[] args) { NotificationService service = new NotificationService(); service.send("ALERT", "Server is down!"); service.send("NEWSLETTER", "June updates are here!"); } }
Що тут не так?
- Код повторюється — створення сповіщень вручну кожного разу.
- Порушується принцип відкритості/закритості — нові типи вимагають модифікації методу send.
- Важко тестувати — тісне зв’язування ускладнює створення моків або розширення.
- Низька масштабованість — що, якщо пізніше потрібно буде надсилати Slack, Push або WhatsApp?
Рішення: Використовуйте патерн Abstract Factory
Abstract Factory допомагає, коли потрібно створювати сімейства пов’язаних об’єктів — наприклад, «комбо» сповіщень, які йдуть разом.
Крок 1: Визначте спільний інтерфейс
public interface Notification { void notifyUser(String message); }
Крок 2: Реалізуйте конкретні сповіщення
public class EmailNotification implements Notification { @Override public void notifyUser(String message) { System.out.println("Sending EMAIL: " + message); } } public class SMSNotification implements Notification { @Override public void notifyUser(String message) { System.out.println("Sending SMS: " + message); } }
Крок 3: Створіть абстрактну фабрику
public interface NotificationAbstractFactory { Notification createPrimaryNotification(); Notification createSecondaryNotification(); }
Крок 4: Створіть конкретні фабрики
public class AlertNotificationFactory implements NotificationAbstractFactory { public Notification createPrimaryNotification() { return new EmailNotification(); } public Notification createSecondaryNotification() { return new SMSNotification(); } } public class NewsletterNotificationFactory implements NotificationAbstractFactory { public Notification createPrimaryNotification() { return new EmailNotification(); } public Notification createSecondaryNotification() { return null; } } public class PromotionNotificationFactory implements NotificationAbstractFactory { public Notification createPrimaryNotification() { return new SMSNotification(); } public Notification createSecondaryNotification() { return null; } }
Крок 5: Використовуйте фабрику у вашому сервісі
public class NotificationService { public void send(NotificationAbstractFactory factory, String message) { Notification primary = factory.createPrimaryNotification(); Notification secondary = factory.createSecondaryNotification(); primary.notifyUser(message); if (secondary != null) { secondary.notifyUser(message); } } public static void main(String[] args) { NotificationService service = new NotificationService(); service.send(new AlertNotificationFactory(), "Server is down!"); service.send(new NewsletterNotificationFactory(), "Check out our new features!"); service.send(new PromotionNotificationFactory(), "50% OFF this weekend!"); } }
Чому це працює краще
- Масштабованість: Додавайте нові комбінації, просто додаючи нову фабрику — без змін у логіці.
- Тестованість: Ви можете створювати моки або заглушки для фабрики та сповіщень для юніт-тестів.
- Слідує принципу відкритості/закритості: Ваша основна логіка залишається недоторканою.
- Чисте розділення обов’язків: Логіка створення живе у фабриках, а не в сервісних класах.
Бонус: Ви все ще можете використовувати провайдер фабрик
Якщо ви хочете динамічно вибирати фабрику на основі рядка або конфігурації:
public class NotificationFactoryProvider { public static NotificationAbstractFactory getFactory(String type) { return switch (type) { case "ALERT" -> new AlertNotificationFactory(); case "NEWSLETTER" -> new NewsletterNotificationFactory(); case "PROMOTION" -> new PromotionNotificationFactory(); default -> throw new IllegalArgumentException("Unknown type: " + type); }; } }
Використання:
NotificationAbstractFactory factory = NotificationFactoryProvider.getFactory("ALERT"); service.send(factory, "System is overheating!");
Заключні думки
Abstract Factory — це чудова еволюція за межі простого Factory Method, коли ви маєте справу з комбінаціями об’єктів або сімействами пов’язаних продуктів. Це особливо корисно в:
- Системах сповіщень
- Темізації UI (наприклад, створення узгоджених темних/світлих віджетів)
- Крос-платформних компонентах
Якщо ваш код починає виглядати як ліс блоків if-else зі створенням об’єктів, захардкодженим всередині них — можливо, настав час для патерну Abstract Factory.
Оригінал статті тут: medium.com/...tory-pattern-0f0c48e0ef96
Відгуки та коментарі вітаються! Буду дуже радий зворотньому зв’язку!
19 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів