Приведи пример класса паттерна проектирования
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерны проектирования: примеры классов и реальное применение
Паттерны проектирования — это решения общих проблем, которые встречаются раз за разом в разработке. Давайте рассмотрим несколько самых важных паттернов с реальными примерами кода, которые встречаются в production'е.
1. Singleton — одна единственная инстанция
Этот паттерн гарантирует, что класс имеет ровно одну инстанцию во всём приложении.
Проблема, которую решает: Логирование, конфигурация, БД соединение — должны быть одни на всё приложение.
Ленивая инициализация (lazy initialization):
public class DatabaseConnection {
private static DatabaseConnection instance;
private Connection connection;
// Приватный конструктор предотвращает создание других инстанций
private DatabaseConnection() {
this.connection = createConnection();
}
// Потокобезопасный getInstance
public static synchronized DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void executeQuery(String sql) {
// используем connection
}
}
// Использование
DatabaseConnection db = DatabaseConnection.getInstance();
db.executeQuery("SELECT * FROM users");
DatabaseConnection db2 = DatabaseConnection.getInstance();
// db и db2 — это ОДИН И ТОТ ЖЕ объект
assert db == db2; // true
Проблема с synchronized: может быть медленно. Лучший способ — Bill Pugh Singleton:
public class DatabaseConnection {
private Connection connection;
private DatabaseConnection() {
this.connection = createConnection();
}
// Статический вложенный класс
private static class SingletonHelper {
private static final DatabaseConnection INSTANCE =
new DatabaseConnection();
}
public static DatabaseConnection getInstance() {
return SingletonHelper.INSTANCE;
// Инициализируется только один раз, потокобезопасно
}
}
Или в Spring:
@Component // или @Service
public class DatabaseConnection {
private Connection connection;
public DatabaseConnection() {
this.connection = createConnection();
}
}
// Spring автоматически создаёт singleton
@Service
public class UserService {
@Autowired
private DatabaseConnection db; // всегда одна инстанция
}
2. Factory — создание объектов по типу
Фабрика инкапсулирует логику создания объектов.
Проблема: В зависимости от типа платежа нужно создать разный объект (Credit Card, PayPal, Bitcoin).
// Интерфейс для всех способов оплаты
public interface PaymentProcessor {
void process(BigDecimal amount);
}
// Конкретные реализации
public class CreditCardProcessor implements PaymentProcessor {
private String cardNumber;
public CreditCardProcessor(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void process(BigDecimal amount) {
System.out.println("Processing " + amount + " with credit card");
// логика оплаты по карте
}
}
public class PayPalProcessor implements PaymentProcessor {
private String email;
public PayPalProcessor(String email) {
this.email = email;
}
@Override
public void process(BigDecimal amount) {
System.out.println("Processing " + amount + " with PayPal");
// логика оплаты через PayPal
}
}
// Фабрика — создаёт нужный процессор
public class PaymentProcessorFactory {
public static PaymentProcessor createProcessor(String type, String credential) {
switch (type) {
case "credit_card":
return new CreditCardProcessor(credential);
case "paypal":
return new PayPalProcessor(credential);
case "bitcoin":
return new BitcoinProcessor(credential);
default:
throw new IllegalArgumentException("Unknown payment type: " + type);
}
}
}
// Использование
public class OrderService {
public void processPayment(String type, String credential, BigDecimal amount) {
PaymentProcessor processor = PaymentProcessorFactory.createProcessor(type, credential);
processor.process(amount);
}
}
В Spring это делается через @Bean и Autowiring:
@Configuration
public class PaymentConfig {
@Bean
public PaymentProcessor creditCardProcessor() {
return new CreditCardProcessor("4532-1234-5678-9010");
}
@Bean
public PaymentProcessor paypalProcessor() {
return new PayPalProcessor("user@example.com");
}
}
@Service
public class OrderService {
@Autowired
private Map<String, PaymentProcessor> processors;
// Spring автоматически inject'ит все PaymentProcessor бины по имени
}
3. Observer — подписка на события
Позволяет объектам подписываться на изменения других объектов.
Проблема: Когда пользователь регистрируется, нужно:
- Отправить приветственное письмо
- Создать аналитику event
- Инициализировать personal account
Вместо того, чтобы писать всё в одном методе, используем Observer:
// Event
public class UserRegisteredEvent {
private Long userId;
private String email;
private LocalDateTime registeredAt;
// getters
}
// Интерфейс Observer'а
public interface UserRegistrationListener {
void onUserRegistered(UserRegisteredEvent event);
}
// Конкретные Observer'ы
@Component
public class EmailNotificationListener implements UserRegistrationListener {
@Override
public void onUserRegistered(UserRegisteredEvent event) {
System.out.println("Sending welcome email to " + event.getEmail());
// отправляем email
}
}
@Component
public class AnalyticsListener implements UserRegistrationListener {
@Override
public void onUserRegistered(UserRegisteredEvent event) {
System.out.println("Recording registration event for user " + event.getUserId());
// логируем в analytics
}
}
@Component
public class AccountInitializationListener implements UserRegistrationListener {
@Override
public void onUserRegistered(UserRegisteredEvent event) {
System.out.println("Creating personal account for user " + event.getUserId());
// создаём account
}
}
// Publisher
@Service
public class UserService {
@Autowired
private List<UserRegistrationListener> listeners; // Spring inject'ит всех
public void registerUser(String email, String password) {
User user = new User(email, password);
userRepository.save(user);
// Publish event
UserRegisteredEvent event = new UserRegisteredEvent(
user.getId(),
email,
LocalDateTime.now()
);
// Уведомляем всех listener'ов
listeners.forEach(listener -> listener.onUserRegistered(event));
}
}
Или через Spring Events (более элегантно):
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void registerUser(String email, String password) {
User user = new User(email, password);
userRepository.save(user);
// Publish event через Spring
eventPublisher.publishEvent(
new UserRegisteredEvent(user.getId(), email)
);
}
}
// Listener'ы подписываются через @EventListener
@Component
public class EmailNotificationListener {
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
// отправляем email
}
}
4. Builder — построение сложных объектов
Построение объектов с множеством параметров.
Проблема: Объект User имеет 10 полей, но при создании нужны только 2-3. Конструктор с 10 параметрами — ужас.
public class User {
private Long id;
private String email;
private String firstName;
private String lastName;
private String phoneNumber;
private LocalDate birthDate;
private String country;
private String city;
private boolean subscribed;
private LocalDateTime createdAt;
// Конструктор через Builder
private User(Builder builder) {
this.id = builder.id;
this.email = builder.email;
this.firstName = builder.firstName;
// и так далее...
}
// Builder класс
public static class Builder {
private Long id;
private String email;
private String firstName;
private String lastName;
private String phoneNumber;
private LocalDate birthDate = LocalDate.now();
private String country = "Unknown";
private String city = "Unknown";
private boolean subscribed = false;
private LocalDateTime createdAt = LocalDateTime.now();
public Builder withId(Long id) {
this.id = id;
return this;
}
public Builder withEmail(String email) {
this.email = email;
return this;
}
public Builder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
// и другие setter'ы...
public User build() {
// Валидация
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("Email is required");
}
return new User(this);
}
}
}
// Использование
User user = new User.Builder()
.withEmail("john@example.com")
.withFirstName("John")
.withLastName("Doe")
.withPhoneNumber("+1234567890")
.withSubscribed(true)
.build(); // Создаём объект только когда готов
Или с Lombok (модернизация):
@Data
@Builder(setterPrefix = "with")
public class User {
private Long id;
private String email;
private String firstName;
private String lastName;
// и так далее
}
// Использование (та же логика, но code генерируется автоматически)
User user = User.builder()
.email("john@example.com")
.firstName("John")
.lastName("Doe")
.build();
5. Adapter — адаптация интерфейсов
Позволяет несовместимым интерфейсам работать вместе.
Проблема:
Есть старый класс LegacyPaymentSystem с методом pay(double amount), а новый code ожидает PaymentProcessor с методом process(BigDecimal amount).
// Старый интерфейс
public class LegacyPaymentSystem {
public void pay(double amount) {
System.out.println("Legacy payment: " + amount);
}
}
// Новый интерфейс
public interface PaymentProcessor {
void process(BigDecimal amount);
}
// Adapter
public class LegacyPaymentAdapter implements PaymentProcessor {
private LegacyPaymentSystem legacySystem;
public LegacyPaymentAdapter(LegacyPaymentSystem legacySystem) {
this.legacySystem = legacySystem;
}
@Override
public void process(BigDecimal amount) {
// Адаптируем новый интерфейс к старому
legacySystem.pay(amount.doubleValue());
}
}
// Использование
LegacyPaymentSystem legacy = new LegacyPaymentSystem();
PaymentProcessor processor = new LegacyPaymentAdapter(legacy);
processor.process(new BigDecimal("100.00")); // Работает!
6. Strategy — выбор алгоритма в runtime
Позволяет выбирать алгоритм в зависимости от условий.
Проблема: Способ сортировки может быть разный в зависимости от данных (QuickSort для больших, InsertionSort для маленьких).
// Интерфейс стратегии
public interface SortingStrategy {
void sort(int[] array);
}
// Конкретные стратегии
public class QuickSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
System.out.println("Sorting with QuickSort");
quickSort(array, 0, array.length - 1);
}
private void quickSort(int[] arr, int low, int high) {
// QuickSort реализация
}
}
public class MergeSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
System.out.println("Sorting with MergeSort");
mergeSort(array);
}
private void mergeSort(int[] arr) {
// MergeSort реализация
}
}
// Context
public class Sorter {
private SortingStrategy strategy;
public Sorter(SortingStrategy strategy) {
this.strategy = strategy;
}
public void performSort(int[] array) {
// Выбираем стратегию в runtime
strategy.sort(array);
}
}
// Использование
int[] smallArray = {3, 1, 2};
Sorter sorter = new Sorter(new MergeSortStrategy()); // для маленьких
sorter.performSort(smallArray);
int[] largeArray = new int[1_000_000];
sorter = new Sorter(new QuickSortStrategy()); // для больших
sorter.performSort(largeArray);
7. Dependency Injection — инверсия управления
Одна из самых важных в современной Java разработке.
// БЕЗ DI — tight coupling
public class UserService {
private UserRepository repo = new UserRepository(); // жёстко привязано
public User findUser(Long id) {
return repo.findById(id);
}
}
// С DI — loose coupling
public class UserService {
private UserRepository repo;
// Инъекция через конструктор
public UserService(UserRepository repo) {
this.repo = repo;
}
public User findUser(Long id) {
return repo.findById(id);
}
}
// Использование
UserRepository repo = new UserRepository();
UserService service = new UserService(repo);
// Или в Spring (автоматическая инъекция)
@Service
public class UserService {
private final UserRepository repo;
@Autowired // или constructor injection (лучше)
public UserService(UserRepository repo) {
this.repo = repo;
}
}
Заключение
Паттерны проектирования — это не теория, это практические решения, которые:
- Делают код более maintainable
- Облегчают тестирование
- Снижают coupling между компонентами
- Упрощают расширение функционала
В Java экосистеме (особенно Spring) многие паттерны уже встроены в framework, но понимание их сути критично для написания хорошего кода.