← Назад к вопросам

Приведи пример класса паттерна проектирования

2.0 Middle🔥 251 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Паттерны проектирования: примеры классов и реальное применение

Паттерны проектирования — это решения общих проблем, которые встречаются раз за разом в разработке. Давайте рассмотрим несколько самых важных паттернов с реальными примерами кода, которые встречаются в 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 — подписка на события

Позволяет объектам подписываться на изменения других объектов.

Проблема: Когда пользователь регистрируется, нужно:

  1. Отправить приветственное письмо
  2. Создать аналитику event
  3. Инициализировать 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, но понимание их сути критично для написания хорошего кода.