Какие паттерны проектирования (Factory, Singleton, Observer, Strategy, Decorator) вы применяли; выберите один и развернуто опишите пример его использования, плюсы, минусы и возможные антипаттерны
Я применял все перечисленные паттерны: Factory, Singleton, Observer, Strategy, Decorator. Ниже — развернуто про Strategy. Описание и когда применять - Strategy — инкапсулирует взаимозаменяемые алгоритмы в отдельные классы и позволяет выбирать алгоритм во время выполнения. - Применять, когда есть несколько способов выполнить операцию (алгоритмы), которые нужно менять без изменения клиента, или когда хочется убрать большие ветвления `if/switch`. Пример (контекст: оплата в интернет-магазине) - Интерфейс стратегии: - `interface PaymentStrategy { void pay(decimal amount); }` - Конкретные стратегии: - `class CreditCardStrategy implements PaymentStrategy { pay(amount) { /* логика карты */ } }` - `class PayPalStrategy implements PaymentStrategy { pay(amount) { /* логика PayPal */ } }` - Контекст (Checkout): - содержит ссылку на `PaymentStrategy`, имеет метод `setStrategy(PaymentStrategy s)` и `executePayment(amount)` который делегирует `strategy.pay(amount)`. - Использование: 1. Клиент выбирает метод оплаты. 2. Контекст получает соответствующую стратегию (через конфигурацию или фабрику). 3. Контекст вызывает `pay`. Короткий псевдокод - `checkout.setStrategy(new PayPalStrategy());` - `checkout.executePayment(100.0);` Плюсы - Разделение обязанностей: алгоритмы вынесены из клиента. - Открытость для расширения / закрытость для модификации (Open/Closed). - Легко тестировать отдельные стратегии. - Позволяет менять поведение во время выполнения. - Убирает громоздкие `if/switch`, улучшает читабельность. Минусы - Увеличение числа классов (при nnn алгоритмах появляется nnn классов). - Клиент или фабрика должны знать, какую стратегию выбирать (управление конфигурацией). - Иногда простая ветка `if` читабельнее для двух-трёх простых вариантов — излишняя абстракция. - Возможны накладные расходы на делегирование (обычно пренебрежимо, выбор/вызов O(1)O(1)O(1)). Возможные антипаттерны - Замена стратегии большим `switch/if` внутри контекста (не используется паттерн, а дублируется логика). - Бог-объект: контекст содержит и управляет всеми деталями стратегий и их состояниями — нарушает инкапсуляцию. - Стратегии с общим изменяемым состоянием (shared mutable state) → проблемы потокобезопасности и неожиданные побочные эффекты. - Использование Singleton для стратегий с состоянием — скрытые зависимости и глобальные состояния. - «Strategy explosion»: слишком мелкие стратегии, каждая реализующая тривиальную логику, усложняющая кодовую базу. Короткое руководство по применению - Используйте, когда поведение действительно варьируется и логика не тривиальна. - Инжектируйте стратегии через фабрику или DI для управления жизненным циклом. - Держите стратегии без состояния или с явно управляемым состоянием, чтобы избежать побочных эффектов. Если нужно — могу привести полный пример кода на выбранном языке.
Описание и когда применять
- Strategy — инкапсулирует взаимозаменяемые алгоритмы в отдельные классы и позволяет выбирать алгоритм во время выполнения.
- Применять, когда есть несколько способов выполнить операцию (алгоритмы), которые нужно менять без изменения клиента, или когда хочется убрать большие ветвления `if/switch`.
Пример (контекст: оплата в интернет-магазине)
- Интерфейс стратегии:
- `interface PaymentStrategy { void pay(decimal amount); }`
- Конкретные стратегии:
- `class CreditCardStrategy implements PaymentStrategy { pay(amount) { /* логика карты */ } }`
- `class PayPalStrategy implements PaymentStrategy { pay(amount) { /* логика PayPal */ } }`
- Контекст (Checkout):
- содержит ссылку на `PaymentStrategy`, имеет метод `setStrategy(PaymentStrategy s)` и `executePayment(amount)` который делегирует `strategy.pay(amount)`.
- Использование:
1. Клиент выбирает метод оплаты.
2. Контекст получает соответствующую стратегию (через конфигурацию или фабрику).
3. Контекст вызывает `pay`.
Короткий псевдокод
- `checkout.setStrategy(new PayPalStrategy());`
- `checkout.executePayment(100.0);`
Плюсы
- Разделение обязанностей: алгоритмы вынесены из клиента.
- Открытость для расширения / закрытость для модификации (Open/Closed).
- Легко тестировать отдельные стратегии.
- Позволяет менять поведение во время выполнения.
- Убирает громоздкие `if/switch`, улучшает читабельность.
Минусы
- Увеличение числа классов (при nnn алгоритмах появляется nnn классов).
- Клиент или фабрика должны знать, какую стратегию выбирать (управление конфигурацией).
- Иногда простая ветка `if` читабельнее для двух-трёх простых вариантов — излишняя абстракция.
- Возможны накладные расходы на делегирование (обычно пренебрежимо, выбор/вызов O(1)O(1)O(1)).
Возможные антипаттерны
- Замена стратегии большим `switch/if` внутри контекста (не используется паттерн, а дублируется логика).
- Бог-объект: контекст содержит и управляет всеми деталями стратегий и их состояниями — нарушает инкапсуляцию.
- Стратегии с общим изменяемым состоянием (shared mutable state) → проблемы потокобезопасности и неожиданные побочные эффекты.
- Использование Singleton для стратегий с состоянием — скрытые зависимости и глобальные состояния.
- «Strategy explosion»: слишком мелкие стратегии, каждая реализующая тривиальную логику, усложняющая кодовую базу.
Короткое руководство по применению
- Используйте, когда поведение действительно варьируется и логика не тривиальна.
- Инжектируйте стратегии через фабрику или DI для управления жизненным циклом.
- Держите стратегии без состояния или с явно управляемым состоянием, чтобы избежать побочных эффектов.
Если нужно — могу привести полный пример кода на выбранном языке.