Средний

Паттерны GoF, которые реально нужны бэкендеру

Урок 2 из 6 в курсе Архитектурные паттерны и подходы к проектированию

Содержание курса (2/6)

Паттерны GoF, которые реально нужны бэкендеру

Из 23 паттернов GoF бэкенд-разработчику в повседневной работе нужны 6–8. Strategy, Observer, Factory, Repository, Decorator — паттерны, которые вы уже используете, даже если не знаете их названий.

Почему это важно: Паттерны — это не про заучивание UML-диаграмм, а про общий словарь и проверенные решения типичных проблем. Когда вы говорите «давай сделаем через Strategy», вся команда сразу понимает структуру.

Главная идея

Паттерн — шаблон решения повторяющейся проблемы. Strategy выносит алгоритм в отдельный объект. Observer уведомляет подписчиков об изменении. Factory скрывает создание сложных объектов. Repository абстрагирует доступ к данным.

Как это выглядит на практике

  1. Задача: уведомить пользователя о заказе по email, SMS и push одновременно
  2. Наивный подход: три вызова внутри OrderService — жёсткая связь
  3. Observer: OrderService публикует событие order.created
  4. EmailNotifier, SmsNotifier, PushNotifier — независимые подписчики
  5. Добавить Telegram-уведомление = создать TelegramNotifier и подписать на событие
  6. OrderService не изменился, тесты OrderService не сломались

Примеры кода

Strategy — вынесение алгоритма

# Проблема: разные способы расчёта доставки
# Без паттерна: if/elsif внутри Order
# С паттерном: каждый алгоритм — отдельный объект

class FlatRateShipping
  def calculate(order)
    500 # 500 рублей за любой заказ
  end
end

class WeightBasedShipping
  RATE_PER_KG = 150

  def calculate(order)
    total_weight = order.items.sum(&:weight_kg)
    (total_weight * RATE_PER_KG).ceil
  end
end

class FreeShippingOver3000
  def initialize(fallback:)
    @fallback = fallback
  end

  def calculate(order)
    order.total >= 3000 ? 0 : @fallback.calculate(order)
  end
end

# Использование
shipping = FreeShippingOver3000.new(
  fallback: WeightBasedShipping.new
)
cost = shipping.calculate(order)

# Новый тариф? Новый класс. Ноль правок в существующих.

Observer — реальный пример на ActiveSupport

# Rails-приложение: после создания заказа нужно
# отправить email, обновить аналитику, зарезервировать товар

# app/models/order.rb
class Order < ApplicationRecord
  after_create_commit :publish_created_event

  private

  def publish_created_event
    ActiveSupport::Notifications.instrument(
      'order.created', order: self
    )
  end
end

# config/initializers/order_subscribers.rb
ActiveSupport::Notifications.subscribe('order.created') do |*, payload|
  order = payload[:order]
  OrderMailer.confirmation(order).deliver_later
end

ActiveSupport::Notifications.subscribe('order.created') do |*, payload|
  order = payload[:order]
  InventoryService.reserve(order.items)
end

ActiveSupport::Notifications.subscribe('order.created') do |*, payload|
  order = payload[:order]
  AnalyticsTracker.track('purchase', amount: order.total)
end

# Добавить Telegram-уведомление:
# 1 файл, 0 изменений в Order

Service Object + Repository

# Service Object инкапсулирует бизнес-операцию
class TransferMoney
  def initialize(account_repo: AccountRepository.new)
    @accounts = account_repo
  end

  def call(from_id:, to_id:, amount:)
    from = @accounts.find(from_id)
    to = @accounts.find(to_id)

    raise InsufficientFunds if from.balance < amount

    @accounts.transaction do
      from.withdraw(amount)
      to.deposit(amount)
      @accounts.save(from)
      @accounts.save(to)
    end

    { from: from.balance, to: to.balance }
  end
end

# Repository абстрагирует доступ к данным
class AccountRepository
  def find(id)
    Account.find(id)
  end

  def save(account)
    account.save!
  end

  def transaction(&block)
    ActiveRecord::Base.transaction(&block)
  end
end

# В тестах подменяем репозиторий на in-memory:
# TransferMoney.new(account_repo: FakeAccountRepo.new)

Что происходит под капотом

  • Strategy заменяет условную логику (if/elsif/case) полиморфизмом — каждая ветка становится классом
  • Observer (pub/sub) разрывает связь между источником события и его обработчиками
  • Factory скрывает сложную логику создания объекта — клиент не знает, какой конкретный класс создаётся
  • Repository отделяет бизнес-логику от способа хранения — можно менять БД без изменения сервисов
  • Decorator оборачивает объект, добавляя поведение (логирование, кэш, валидация) без наследования

Типичные ошибки и заблуждения

  • «Нужно знать все 23 паттерна GoF» — на практике 6–8 покрывают 90% задач бэкендера
  • «Паттерн = больше кода» — да, но этот код легче менять, тестировать и понимать в команде
  • «Паттерны устарели» — названия из 1994, но проблемы те же: условная логика, жёсткая связь, сложное создание объектов

Ключевые выводы

  • Strategy: разные алгоритмы (доставка, оплата, скидки) — отдельные объекты с одинаковым интерфейсом
  • Observer: событийная модель разрывает связь между «что произошло» и «что делать»
  • Repository + Service Object: бизнес-логика отделена от фреймворка и хранения

Термины урока

{:term=>"Strategy", :definition=>"Паттерн, выносящий семейство алгоритмов в отдельные объекты с одинаковым интерфейсом, позволяя переключать их динамически"}
{:term=>"Observer (Pub/Sub)", :definition=>"Паттерн, при котором объект уведомляет подписчиков о событии, не зная, кто они"}
{:term=>"Repository", :definition=>"Паттерн, абстрагирующий доступ к данным за интерфейсом коллекции, скрывая SQL/ORM от бизнес-логики"}

Связь с работой backend-разработчика

Паттерны — общий язык команды и проверенные решения. Если вы видите растущий if/elsif — это Strategy. Если хардкодите вызовы после события — это Observer. Если бизнес-логика пронизана SQL-запросами — это Repository.

Мини-разбор реальной ситуации

Финтех-стартап: расчёт комиссии содержал case-оператор на 200 строк (15 типов транзакций). Каждая новая комиссия — 2 дня работы и ручное регрессионное тестирование всех 15 веток. После рефакторинга на Strategy (15 маленьких классов по 20–30 строк) добавление новой комиссии занимает 1–2 часа, тестируется изолированно.

Что запомнить

  • Strategy заменяет if/elsif полиморфизмом — новое поведение = новый класс
  • Observer (pub/sub) = источник не знает о подписчиках, подписчики не знают друг о друге
  • Service Object + Repository = тестируемая бизнес-логика, независимая от фреймворка

Итог

Паттерны — не академическое упражнение, а практические решения проблем, с которыми вы сталкиваетесь каждый день. Strategy, Observer, Repository, Service Object — четыре паттерна, которые кардинально улучшают структуру backend-кода.

Комментарии к уроку

Войдите, чтобы оставить комментарий.

Пока нет комментариев — будьте первым.