Какие паттерны проектирования (design patterns) вы знаете и в каких случаях они применяются?

Ответ

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

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

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

  • Singleton (Одиночка): Гарантирует, что у класса существует только один экземпляр, и предоставляет глобальную точку доступа к нему.
    • Применение: Логгер, подключение к базе данных, кэш, настройки приложения.
      public class DatabaseConnection {
      private static DatabaseConnection instance;
      private DatabaseConnection() {} // Приватный конструктор
      public static DatabaseConnection getInstance() {
          if (instance == null) {
              instance = new DatabaseConnection();
          }
          return instance;
      }
      }
  • Factory Method (Фабричный метод): Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемых объектов.
    • Применение: Создание объектов, тип которых зависит от контекста (например, разные виды отчетов или уведомлений).
  • Builder (Строитель): Позволяет создавать сложные объекты пошагово, используя один и тот же процесс конструирования.
    • Применение: Создание объектов со множеством необязательных параметров или сложной конфигурацией (например, HTTP-клиент, SQL-запрос).

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

Отвечают за композицию классов и объектов, формируя крупные структуры.

  • Adapter (Адаптер): Позволяет объектам с несовместимыми интерфейсами работать вместе.
    • Применение: Интеграция старого кода с новым API, использование сторонних библиотек.
  • Decorator (Декоратор): Динамически добавляет объекту новые обязанности, являясь гибкой альтернативой наследованию.
    • Применение: Добавление функциональности (логирование, шифрование, сжатие) к основному объекту без изменения его класса.

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

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

  • Observer (Наблюдатель): Определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются.
    • Применение: Системы событий (event-driven), MVC (модель уведомляет представления).
  • Strategy (Стратегия): Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми.
    • Применение: Выбор алгоритма сортировки, способа оплаты (карта, PayPal), алгоритма валидации в runtime.

Для тестирования особенно полезны:

  • Factory и Builder — для создания сложных тестовых данных и объектов-заглушек.
  • Proxy — для создания моков (mock objects) и перехвата вызовов в unit-тестах.

Ответ 18+ 🔞

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

1. Порождающие (Creational) — те, что объекты штампуют

Их задача — упростить и обезопасить процесс создания новых сущностей. Чтобы не было "ой, а я тут накосячил с конструктором, теперь всё падает".

  • Singleton (Одиночка): Это когда ты хочешь, чтобы какой-то важный мудак был в системе в одном-единственном экземпляре. Типа главного по базе данных или логгеру. Все ходят к нему и просят одно и то же. Сделать его — проще простого, но потом за ним, как за золотым унитазом, следить надо.
    public class DatabaseConnection {
        private static DatabaseConnection instance; // Вот он, наш царь и бог, в единственном числе
        private DatabaseConnection() {} // Конструктор приватный, чтобы всякие шутники не плодили клонов
        public static DatabaseConnection getInstance() {
            if (instance == null) { // Если ещё не создан...
                instance = new DatabaseConnection(); // ...то вот, получай, создаём.
            }
            return instance; // А дальше всегда отдаём одного и того же
        }
    }
  • Factory Method (Фабричный метод): Представь, тебе нужен "отчёт". Но отчёты бывают разные: PDF, Excel, HTML. Вместо того чтобы везде писать if (type == "pdf") new PdfReport(), ты заводишь фабрику. Она, хитрая жопа, сама решает, какую конкретную хрень тебе выдать, исходя из твоих пожеланий. Красота, а не жизнь.
  • Builder (Строитель): Это спасение, когда у объекта параметров, как у мартышлюшки, овердохуища, и половина из них необязательные. Вместо конструктора на 15 аргументов, где половину надо передавать null, ты пишешь: new Pizza.Builder().size(LARGE).addCheese().addPepperoni().build(). Чётко, ясно, и в жопу путаницу.

2. Структурные (Structural) — те, что из кусочков собирают целое

Они про то, как разные классы, которые друг друга в гробу видали, заставить работать вместе, не переписывая половину кодовой базы.

  • Adapter (Адаптер): Классика! У тебя есть старый, проверенный годами класс СтарыйПринтер, который печатает только в формате "БИП-БИП-БУМ". А новая система требует интерфейс СовременнаяПечать. Ты не переписываешь старика, а пишешь адаптер — прослойку, которая принимает вызов нового интерфейса и внутри, ёпта, дергает старого деда с его "БИП-БИП-БУМ". Всё работает, все довольны.
  • Decorator (Декоратор): Вообрази основной объект — "Файл". Тебе надо добавить ему логирование при чтении, потом — шифрование, потом — сжатие. Наследовать? Так это же дерево классов высотой с Эверест получится! А декоратор — это как матрёшка. Берешь "Файл", оборачиваешь в "ДекораторЛогирования", его — в "ДекораторШифрования". Каждый слой добавляет свою фишку. Гениально и гибко, как хуй в пальто.

3. Поведенческие (Behavioral) — те, что раздают роли и организуют общение

Тут уже не про создание или структуру, а про то, как объекты друг с другом болтают и кто за что отвечает.

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

А для тестирования, это, сука, вообще золотая жила:

  • Factory и Builder — чтобы не ебаться с созданием сложных тестовых объектов, у которых половина полей — заглушки. Собрал билдером — и поехали.
  • Proxy — вот где моки (mock objects) настоящую силу берут! Можно подсунуть прокси-объект, который перехватывает вызовы методов и говорит: "Всё, ребята, я тут вместо настоящего сервиса, вот вам заранее приготовленные ответы, а вы проверяйте, что вас вызывали с правильными параметрами". Без этого юнит-тестирование — просто пиздец и мрак.