Какие паттерны проектирования вы знаете? Приведите примеры их использования.

«Какие паттерны проектирования вы знаете? Приведите примеры их использования.» — вопрос из категории Паттерны, который задают на 35% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерны проектирования — это типовые, проверенные решения часто встречающихся проблем в проектировании ПО. Они делятся на три основные категории.

1. Порождающие паттерны (Creational)

Управляют процессом создания объектов, делая его более гибким и независимым.

  • Singleton (Одиночка): Гарантирует, что у класса существует только один экземпляр, и предоставляет глобальную точку доступа к нему.
    public class Logger { // Пример логгера
        private static volatile Logger instance;
        private Logger() {} // Приватный конструктор
        public static Logger getInstance() {
            if (instance == null) { // Double-Checked Locking для потокобезопасности
                synchronized (Logger.class) {
                    if (instance == null) {
                        instance = new Logger();
                    }
                }
            }
            return instance;
        }
        public void log(String message) { /* ... */ }
    }
    // Использование: Logger.getInstance().log("Message");
  • Factory Method (Фабричный метод): Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта.
  • Abstract Factory (Абстрактная фабрика): Создает семейства связанных объектов без указания их конкретных классов.
  • Builder (Строитель): Позволяет создавать сложные объекты пошагово, отделяя конструирование от представления. Полезен для объектов со множеством необязательных параметров.
    public class Computer {
        private final String CPU; // обязательный
        private final String RAM; // обязательный
        private final String storage; // необязательный
        private final String graphicsCard; // необязательный
        // Приватный конструктор, принимающий Builder
        private Computer(Builder builder) {
            this.CPU = builder.CPU;
            this.RAM = builder.RAM;
            this.storage = builder.storage;
            this.graphicsCard = builder.graphicsCard;
        }
        public static class Builder {
            private final String CPU;
            private final String RAM;
            private String storage;
            private String graphicsCard;
            public Builder(String cpu, String ram) { this.CPU = cpu; this.RAM = ram; }
            public Builder storage(String storage) { this.storage = storage; return this; }
            public Builder graphicsCard(String gpu) { this.graphicsCard = gpu; return this; }
            public Computer build() { return new Computer(this); }
        }
    }
    // Использование: Computer pc = new Computer.Builder("Intel i7", "16GB").storage("1TB SSD").build();

2. Структурные паттерны (Structural)

Объединяют классы и объекты в более крупные структуры.

  • Adapter (Адаптер): Позволяет объектам с несовместимыми интерфейсами работать вместе. Преобразует интерфейс одного класса в интерфейс, ожидаемый клиентом.
    // Старая система
    public class LegacyPrinter {
        public void printDocument(String text) { /* печать */ }
    }
    // Новый интерфейс, который требуется клиенту
    public interface ModernPrinter {
        void print(String content);
    }
    // Адаптер
    public class PrinterAdapter implements ModernPrinter {
        private LegacyPrinter legacyPrinter;
        public PrinterAdapter(LegacyPrinter printer) { this.legacyPrinter = printer; }
        @Override
        public void print(String content) {
            legacyPrinter.printDocument(content); // Адаптация вызова
        }
    }
  • Decorator (Декоратор): Динамически добавляет объекту новые обязанности, являясь гибкой альтернативой наследованию для расширения функциональности.
  • Proxy (Заместитель): Предоставляет объект-заменитель, который контролирует доступ к другому объекту (ленивая инициализация, кеширование, защита).
  • Facade (Фасад): Предоставляет простой интерфейс к сложной подсистеме, скрывая ее детали.

3. Поведенческие паттерны (Behavioral)

Решают задачи эффективного взаимодействия и распределения ответственности между объектами.

  • Observer (Наблюдатель): Определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически. Основа event-систем.
    public interface Observer {
        void update(String event);
    }
    public class ConcreteObserver implements Observer {
        private String name;
        @Override
        public void update(String event) { System.out.println(name + " received: " + event); }
    }
    public class Subject {
        private List<Observer> observers = new ArrayList<>();
        public void addObserver(Observer o) { observers.add(o); }
        public void notifyObservers(String event) {
            for (Observer o : observers) { o.update(event); }
        }
    }
  • Strategy (Стратегия): Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Позволяет изменять алгоритм независимо от клиента, который его использует.
  • Command (Команда): Инкапсулирует запрос как объект, позволяя параметризовать клиентов с различными запросами, ставить запросы в очередь или поддерживать отмену операций.