Какие паттерны проектирования ты знаешь и применял?

Ответ

В своей практике я чаще всего применяю следующие паттерны, разделяя их по целям:

Порождающие (Creational):

  • Фабричный метод (Factory Method) / Абстрактная фабрика (Abstract Factory): Использовал для создания семейств связанных объектов, например, различных провайдеров аутентификации (OAuth, JWT, LDAP) или парсеров разных форматов логов (JSON, XML, CSV). Это позволяет изолировать логику создания.
  • Строитель (Builder): Применял для пошагового конструирования сложных объектов конфигурации или DTO с множеством необязательных полей, что делает код чище, чем вызов конструктора с десятком параметров.
  • Внедрение зависимостей (Dependency Injection): Основа большинства современных фреймворков. Активно использую для повышения тестируемости и уменьшения связности компонентов.

Структурные (Structural):

  • Адаптер (Adapter): Интегрировал сторонние библиотеки или legacy-код, создавая класс-обертку с нужным интерфейсом.
  • Декоратор (Decorator): Использовал для динамического добавления функциональности, например, добавления логирования, кэширования или проверки прав доступа к основному сервису без изменения его кода.
  • Фасад (Facade): Создавал упрощенные интерфейсы для работы со сложными подсистемами (например, модулем отчетности или интеграцией с внешним API).

Поведенческие (Behavioral):

  • Стратегия (Strategy): Применял для инкапсуляции алгоритмов, которые могут меняться (например, алгоритмы сортировки, стратегии расчета скидок или способы отправки уведомлений — email, SMS, push). Это позволяет легко подменять их в runtime.
  • Наблюдатель (Observer): Реализовывал для построения систем событий (event-driven). Например, при изменении статуса заказа автоматически запускались обработчики для отправки уведомления клиенту, обновления склада и логирования.
  • Команда (Command): Использовал для инкапсуляции запросов как объектов, что полезно для реализации очереди задач, отмены операций (undo/redo) или отложенного выполнения.

Пример Стратегии на Java для валидации:

// Интерфейс стратегии
interface ValidationStrategy {
    boolean execute(String data);
}

// Конкретные стратегии
class EmailValidation implements ValidationStrategy {
    public boolean execute(String data) {
        return data.matches("^[^@]+@[^@]+\.[^@]+$");
    }
}
class PhoneValidation implements ValidationStrategy {
    public boolean execute(String data) {
        return data.matches("^\+?[0-9]{10,}$");
    }
}

// Контекст, использующий стратегию
class Validator {
    private ValidationStrategy strategy;
    public Validator(ValidationStrategy strategy) {
        this.strategy = strategy;
    }
    public boolean validate(String input) {
        return strategy.execute(input);
    }
}

// Использование
Validator emailValidator = new Validator(new EmailValidation());
boolean isValid = emailValidator.validate("test@example.com");

Ключевой принцип — не использовать паттерны ради самих паттернов, а применять их для решения конкретных проблем проектирования, таких как снижение связанности, повышение гибкости или улучшение тестируемости кода.

Ответ 18+ 🔞

Слушай, я тут подумал, про паттерны эти ваши. Ну, в работе-то они реально нужны, не просто так их придумали, ёпта. Расскажу, как я их юзаю, чтобы не было этой пиздопроебибны в коде.

Порождающие (Creational):

  • Фабричный метод / Абстрактная фабрика: Вот, например, когда у тебя куча разных способов залогиниться — через OAuth, через JWT, ещё какую-то хуйню. Так вот, вместо того чтобы везде if-else городить, ты эту логику создания в отдельное место засовываешь. Получается чисто, и когда новый способ добавишь — овердохуища кода переписывать не надо.
  • Строитель (Builder): А это вообще спасение, когда объект собирать надо из кучи полей, половина из которых необязательные. Представь конструктор с десятью параметрами — это же пиздец, в глазах рябит. А так — по кирпичику, красиво. Сам от себя охуевал, когда раньше без него делал.
  • Внедрение зависимостей (Dependency Injection): Ну это основа основ, без этого сейчас нихуя не работает. Всё ради тестов, чувак. Чтобы не приклеено было намертво, а можно было подсунуть заглушку и проверить.

Структурные (Structural):

  • Адаптер (Adapter): Бывает, подключаешь какую-нибудь старую библиотеку, написанную, будто на дворе 2002-й год. Интерфейс у неё — манда с ушами. Вот тут и пишешь обёрточку, которая твой красивый код с этим уродцем подруживает. Спасение, блядь.
  • Декоратор (Decorator): О, это мощно. Допустим, есть у тебя сервис, который данные отдаёт. А потом понадобилось логирование добавить, потом кэширование. Так вот, вместо того чтобы в этот сервис лезть и всё там ломать, ты просто оборачиваешь его в декораторы. Как матрёшка. Добавил функциональность, а основной код даже не чихнул.
  • Фасад (Facade): Это когда внутри система — ёперный театр, куча классов, всё громоздкое. А снаружи ты делаешь одну простую дверцу, через которую всё работает. Упрощение — наше всё, чтобы не ебать мозг себе и другим.

Поведенческие (Behavioral):

  • Стратегия (Strategy): Один из любимых, серьёзно. Допустим, у тебя есть алгоритм, который может меняться. Скажем, способ расчёта скидки: для новых клиентов один, для постоянных — другой, ещё акция какая-то. Вместо кучи switch или if — ты каждый способ в отдельный класс засовываешь. И потом просто подменяешь одну стратегию на другую. Гибко, как хуй с горы.
  • Наблюдатель (Observer): Событийная модель, всё такое. Сделал что-то — разослал уведомления всем, кто подписан. Заказ оформили — клиенту письмо, складу сигнал, в лог запись. Все довольны, никто никого не дергает.
  • Команда (Command): Это когда нужно действие в объект завернуть. Полезно для истории операций (чтоб откатить можно было), или для отложенного выполнения. Поставил команду в очередь — и пусть работает, когда сможет.

Вот, смотри, пример той самой Стратегии на Java, чтобы было понятнее, о чём речь:

// Интерфейс стратегии
interface ValidationStrategy {
    boolean execute(String data);
}

// Конкретные стратегии
class EmailValidation implements ValidationStrategy {
    public boolean execute(String data) {
        return data.matches("^[^@]+@[^@]+\.[^@]+$");
    }
}
class PhoneValidation implements ValidationStrategy {
    public boolean execute(String data) {
        return data.matches("^\+?[0-9]{10,}$");
    }
}

// Контекст, использующий стратегию
class Validator {
    private ValidationStrategy strategy;
    public Validator(ValidationStrategy strategy) {
        this.strategy = strategy;
    }
    public boolean validate(String input) {
        return strategy.execute(input);
    }
}

// Использование
Validator emailValidator = new Validator(new EmailValidation());
boolean isValid = emailValidator.validate("test@example.com");

Главное, чувак, — не становиться фанатиком. Не нужно впихивать паттерны куда ни попадя, лишь бы было. Это как молоток: если всё вокруг видишь гвоздем, то всё вокруг и будешь им долбить. Используй их, только когда реальная проблема есть: код связан сильнее, чем сиамские близнецы, или расширять его — терпения ноль ебать. Тогда да, тогда они выручают.

Видео-ответы