> Паттерны проектирования в реальных проектах: когда и зачем их применять

Разбираемся, когда паттерны проектирования действительно нужны, а когда превращают код в переусложнённую конструкцию. На примерах Factory, Observer и Strategy - с кодом и реальными сценариями из разработки.

05.08.2026

Паттерны проектирования - одна из тех тем, которые вызывают споры на каждом собеседовании. Одни кандидаты сыплют терминами и диаграммами, другие честно признаются, что в работе ни разу не использовали ни один из них. Истина, как обычно, посередине: паттерны - это не обязательный ритуал, а набор проверенных решений для типовых задач. Вопрос не в том, знаете ли вы их, а в том, умеете ли вовремя распознать ситуацию, где они действительно упрощают жизнь.

В этой статье разберём три популярных паттерна - Factory, Observer и Strategy - с примерами кода и реальными сценариями. А затем поговорим о том, как не превратить проект в музей абстракций.

Factory: когда создание объектов становится сложным

Паттерн Factory решает проблему создания объектов, когда конкретный тип заранее неизвестен или логика создания разрослась до неприличных размеров. Вместо того чтобы разбрасывать new по всему коду, выносим создание в отдельный метод или класс.

Рассмотрим типичный пример: система уведомлений, которая должна отправлять сообщения через разные каналы - email, SMS, push. Без фабрики код выглядел бы так:

PYTHON
# Без фабрики
def send_notification(channel, message):
if channel == "email":
notifier = EmailNotifier()
elif channel == "sms":
notifier = SMSNotifier()
elif channel == "push":
notifier = PushNotifier()
else:
raise ValueError(f"Unknown channel: {channel}")
notifier.send(message)

Каждый раз, когда добавляется новый канал, приходится лезть в этот метод и дописывать ещё одну ветку. Через пару месяцев функция превращается в простыню из if-elif. Фабрика решает эту проблему:

PYTHON
# С фабрикой
class NotifierFactory:
@staticmethod
def create(channel):
notifiers = {
"email": EmailNotifier,
"sms": SMSNotifier,
"push": PushNotifier,
}
notifier_class = notifiers.get(channel)
if notifier_class is None:
raise ValueError(f"Unknown channel: {channel}")
return notifier_class()
def send_notification(channel, message):
notifier = NotifierFactory.create(channel)
notifier.send(message)

Теперь добавление нового канала - это одна строка в словаре, а не новая ветка условий. Код стал проще тестировать: можно подменить фабрику моком и проверить, что вызывается правильный класс.

На собеседовании часто спрашивают, чем Factory отличается от Abstract Factory. Первый инкапсулирует создание одного объекта, второй - семейства связанных объектов. Но для большинства реальных задач достаточно простой фабрики или даже фабричного метода.

Observer: реакция на изменения без жёстких связей

Observer - паттерн, который лежит в основе событийно-ориентированной архитектуры. Суть проста: есть субъект (subject), который хранит список подписчиков (observers) и уведомляет их о своих изменениях. Подписчики не знают друг о друге, а субъект не знает, кто именно подписан.

Классический пример - UI-компоненты, которые должны обновляться при изменении данных. Допустим, есть корзина покупок, и при добавлении товара нужно обновить счётчик на иконке, пересчитать сумму и, возможно, показать рекомендации.

PYTHON
class Cart:
def __init__(self):
self._items = []
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def add_item(self, item):
self._items.append(item)
self._notify()
def _notify(self):
for observer in self._observers:
observer.update(self)
class CartCounter:
def update(self, cart):
print(f"Items in cart: {len(cart._items)}")
class CartTotal:
def update(self, cart):
total = sum(item.price for item in cart._items)
print(f"Total: {total}")
cart = Cart()
cart.attach(CartCounter())
cart.attach(CartTotal())
cart.add_item(Item("Book", 500))

В этом примере субъект - корзина, наблюдатели - счётчик и сумматор. Они не знают о существовании друг друга, и добавить новый обработчик (например, аналитику) можно без изменения кода корзины.

Однако у Observer есть обратная сторона: при большом количестве подписчиков сложно отследить, кто и когда обновился. Если неаккуратно управлять подписками, легко получить утечки памяти или неожиданные каскадные обновления. Поэтому в реальных проектах часто используют готовые реализации - например, EventEmitter в Node.js или Observable в RxJS, чтобы не изобретать велосипед.

Strategy: взаимозаменяемые алгоритмы

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

Типичный пример - расчёт стоимости доставки. В зависимости от способа доставки (курьер, почта, самовывоз) формула разная. Без паттерна пришлось бы писать:

PYTHON
def calculate_delivery(order, method):
if method == "courier":
return 300 + order.weight * 10
elif method == "post":
return 150 + order.weight * 5
elif method == "pickup":
return 0
else:
raise ValueError(f"Unknown method: {method}")

Со Strategy:

PYTHON
class DeliveryStrategy:
def calculate(self, order):
raise NotImplementedError
class CourierDelivery(DeliveryStrategy):
def calculate(self, order):
return 300 + order.weight * 10
class PostDelivery(DeliveryStrategy):
def calculate(self, order):
return 150 + order.weight * 5
class PickupDelivery(DeliveryStrategy):
def calculate(self, order):
return 0
class Order:
def __init__(self, weight, strategy):
self.weight = weight
self.strategy = strategy
def delivery_cost(self):
return self.strategy.calculate(self)

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

Strategy особенно полезен, когда алгоритмов много и они могут меняться независимо от основного класса. Но если вариантов всего два-три и они вряд ли расширятся, проще оставить простой if - это не будет ошибкой.

Когда паттерны становятся злом: overengineering

Главная опасность паттернов - их неуместное применение. Начинающие разработчики, насмотревшись на "идеальные" примеры, начинают натягивать паттерны на любую задачу. В результате получается код, где для сложения двух чисел нужно создать фабрику, синглтон и наблюдателя.

Признаки overengineering:

  • Классов и интерфейсов больше, чем реальных действий.

  • Для простой задачи приходится открывать пять файлов, чтобы проследить логику.

  • Паттерн добавляет абстракцию, но не решает конкретной проблемы.

  • Код стал сложнее читать, хотя задача осталась прежней.

Например, если у вас всего один способ создания объекта, фабрика не нужна. Если уведомления отправляются только по email, Observer избыточен. Если алгоритм один и не планируется расширение, Strategy - лишняя прослойка.

Хороший критерий - правило трёх: если вы видите, что похожий код повторяется в трёх местах, тогда можно подумать о паттерне. Если повторений нет - скорее всего, паттерн не нужен.

Практические кейсы из реальной разработки

Приведём несколько ситуаций, где паттерны действительно выручают.

Кейс 1: Плагинная архитектура

В одном проекте была система, которая позволяла подключать модули для обработки заказов. Каждый модуль делал что-то своё: проверял наличие товара, считал налог, отправлял уведомление. Вместо того чтобы хардкодить порядок вызовов, использовали цепочку обязанностей (Chain of Responsibility) - каждый модуль решал, обработать запрос или передать дальше. Это позволило добавлять новые модули без изменения ядра.

Кейс 2: Работа с API разных провайдеров

Приложение интегрировалось с несколькими платёжными системами. У каждой свой формат запросов и ответов. Чтобы не плодить условные операторы, использовали Adapter - общий интерфейс для всех провайдеров, а внутри каждый адаптер преобразовывал данные в нужный формат. Это упростило тестирование и добавление нового провайдера.

Кейс 3: Кэширование с разными стратегиями

В сервисе аналитики были разные стратегии кэширования: на короткое время, на длительное, с инвалидацией по событию. Вместо того чтобы зашивать логику в каждый метод, использовали Strategy - каждый тип кэша реализовывал один интерфейс. Это позволило легко переключать стратегии в конфигурации.

Как отвечать на вопросы о паттернах на собеседовании

На собеседованиях часто просят не просто перечислить паттерны, а объяснить, когда их стоит применять. Вот несколько советов.

Во-первых, не заучивайте определения наизусть. Лучше приведите пример из своей практики, пусть даже учебный. Расскажите, какую проблему решал паттерн и что было бы без него. Это покажет, что вы понимаете суть, а не просто запомнили термины.

Во-вторых, будьте готовы критически оценить использование паттерна. Если интервьюер спрашивает, почему в конкретном случае не подойдёт Singleton, честно скажите, что Singleton часто усложняет тестирование и создаёт скрытые зависимости. Умение видеть недостатки - сильная сторона.

В-третьих, не пытайтесь впихнуть паттерн в ответ, если он не нужен. Лучше сказать: "Здесь достаточно простого if, потому что вариантов мало и они вряд ли изменятся", - это покажет зрелость мышления.

Итоги

Паттерны проектирования - это не самоцель, а инструмент. Они полезны, когда задача действительно повторяется и требует гибкости. Factory упрощает создание объектов, Observer - реакцию на изменения, Strategy - взаимозаменяемость алгоритмов. Но каждый паттерн добавляет уровень абстракции, и этот уровень должен окупаться.

При подготовке к собеседованию стоит не просто выучить список паттернов, а научиться видеть, где они уместны. Попробуйте проанализировать свой текущий проект: какие проблемы возникают чаще всего? Возможно, один из паттернов уже негласно используется, просто не назван своим именем. Умение распознать паттерн в коде - навык, который ценится выше, чем умение его нарисовать на доске.

И помните: лучший код - тот, который легко читать и менять. Если паттерн мешает этому, от него стоит отказаться, даже если он "правильный" по учебнику.

> Похожие публикации

31.07.2026

Git для повседневной работы: команды и сценарии, которые сэкономят часы

Практическое руководство по Git для разработчиков: топ-10 команд, алгоритм разрешения конфликтов и сценарии, которые ускоряют повседневную работу и экономят часы.

Soft Skills для разработчика или почему код - это только половина дела
03.07.2026

Soft Skills для разработчика или почему код - это только половина дела

05.08.2026

Что спрашивают на собеседовании по системному дизайну: разбор типовых задач

Разбор типовых задач на собеседовании по системному дизайну: от проектирования чата и новостной ленты до CDN. Рассмотрены структура ответа, типичные ошибки и практические способы подготовки.

04.08.2026

Как отвечать на вопрос "Расскажи о себе" на техническом собеседовании

Разбираем, как правильно отвечать на вопрос "Расскажи о себе" на техническом собеседовании: структура "прошлое-настоящее-будущее", примеры сильных и слабых формулировок, и как связать свою историю с требованиями вакансии.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью