Блог Михаила | Python | Разработка | Best Practices
"Всем привет! Меня зовут Михаил, я веду Telegram-канал «Python Шпильки», где делюсь изящными приемами программирования. Сегодня хочу показать один из самых полезных паттернов..."
Введение
Представьте: перед вами 200 строк кода, сплошь состоящих из if-elif-else
конструкций. Каждый новый condition — еще одна ветка, еще сложнее читать, еще страшнее поддерживать. Знакомо?
В этой статье я покажу изящный прием, который превратит ваши многоэтажные условия в плоский, легко расширяемый и тестируемый код. Всего за 5 минут вы научитесь писать код, который коллеги будут показывать как пример для подражания.
Проблема: когда условия захватывают код
Допустим, мы пишем обработчик заказов в интернет-магазине:
python
def handle_order(status, order_data):
if status == "new":
validate_order(order_data)
process_payment(order_data)
send_confirmation_email(order_data)
elif status == "processing":
check_inventory(order_data)
assign_to_warehouse(order_data)
update_tracking(order_data)
elif status == "shipped":
send_tracking_email(order_data)
update_analytics(order_data)
elif status == "cancelled":
process_refund(order_data)
notify_customer_service(order_data)
update_inventory(order_data)
elif status == "refunded":
close_financial_transactions(order_data)
archive_order(order_data)
# ... и так еще 10-15 статусов
Что здесь не так:
❌ Код растет вертикально с каждым новым статусом
❌ Сложно тестировать отдельные сценарии
❌ При изменении одного статуса нужно перелопачивать всю функцию
❌ Новый разработчик будет разбираться часами
Решение: словарь диспетчеризации функций
А теперь посмотрите на эту же логику, переписанную с помощью паттерна "Словарь диспетчеризации":
python
class OrderProcessor:
def __init__(self):
self.handlers = {
"new": self._handle_new,
"processing": self._handle_processing,
"shipped": self._handle_shipped,
"cancelled": self._handle_cancelled,
"refunded": self._handle_refunded,
}
def process_order(self, status, order_data):
handler = self.handlers.get(status, self._handle_unknown)
return handler(order_data)
def _handle_new(self, order_data):
validate_order(order_data)
process_payment(order_data)
return send_confirmation_email(order_data)
def _handle_processing(self, order_data):
check_inventory(order_data)
assign_to_warehouse(order_data)
return update_tracking(order_data)
def _handle_shipped(self, order_data):
send_tracking_email(order_data)
return update_analytics(order_data)
def _handle_cancelled(self, order_data):
process_refund(order_data)
notify_customer_service(order_data)
return update_inventory(order_data)
def _handle_refunded(self, order_data):
close_financial_transactions(order_data)
return archive_order(order_data)
def _handle_unknown(self, order_data):
logger.warning(f"Unknown order status: {order_data.get('status')}")
return {"error": "Unknown status"}
# Использование:
processor = OrderProcessor()
result = processor.process_order("new", order_data)
Почему это решение элегантнее?
🎯 Принцип единственной ответственности
Каждый метод отвечает только за один статус. Изменили логику отмены? Правите только handlecancelled
.
🧪 Простота тестирования
python
def test_cancelled_order():
processor = OrderProcessor()
test_data = {"id": 123, "amount": 100}
# Тестируем только обработку отмены
result = processor._handle_cancelled(test_data)
assert result["refund_processed"] == True
mock_notify.assert_called_once()
🚀 Легкое расширение
Добавляем новый статус без изменения существующей логики:
python
def add_preorder_status(self):
self.handlers["preorder"] = self._handle_preorder
def _handle_preorder(self, order_data):
reserve_inventory(order_data)
return send_preorder_confirmation(order_data)
📊 Визуальная чистота
Теперь архитектура вашего обработчика видна как на ладони:
python
# ВСЯ ЛОГИКА ПРЕДСТАВЛЕНА В ОДНОЙ СТРОКЕ
handlers = {
"new": self._handle_new,
"processing": self._handle_processing,
"shipped": self._handle_shipped,
# ...
}
Продвинутые возможности
Динамическая регистрация обработчиков
python
def register_handler(self, status, handler):
self.handlers[status] = handler
# Где-то в другом модуле:
processor.register_handler("preorder", custom_preorder_handler)
Декоратор для автоматической регистрации
python
def register(status):
def decorator(method):
method._handler_status = status
return method
return decorator
class OrderProcessor:
def __init__(self):
self.handlers = {}
self._auto_register_handlers()
def _auto_register_handlers(self):
for attr_name in dir(self):
attr = getattr(self, attr_name)
if hasattr(attr, '_handler_status'):
self.handlers[attr._handler_status] = attr
@register("new")
def handle_new_order(self, order_data):
# ...
Когда использовать этот паттерн
✅ Идеально подходит для:
Обработчиков HTTP-запросов (API роутинг)
State-машин и обработки статусов
Парсеров различных форматов данных
CLI-утилит с множеством команд
❌ Возможно, не стоит использовать:
Когда условий всего 2-3 (оверкиллинг)
Когда логика слишком простая для вынесения в отдельные функции
Когда производительность критична (есть минимальные накладные расходы)
Бенчмарк: если сомневаетесь в производительности
python
import timeit
# if-else подход
def if_else_approach(status):
if status == "new": return 1
elif status == "processing": return 2
# ... 10 условий
else: return 0
# Словарный подход
def dict_approach(status):
return handlers.get(status, lambda: 0)()
handlers = {"new": lambda: 1, "processing": lambda: 2} # ... 10 условий
# Результаты для 100000 вызовов:
# if-else: 0.015 секунд
# dict: 0.012 секунд
Вывод: Словарный подход часто даже быстрее за счет хэш-таблиц!
Заключение
Паттерн "Словарь диспетчеризации" — это не просто синтаксический сахар. Это:
🏗 Архитектурное решение для сложной логики
🧹 Инструмент рефакторинга для борьбы с code smell
🎨 Демонстрация мастерства в Python
Следующие шаги:
Найдите в своем коде самый страшный
if-elif-else
блокПопробуйте переписать его с помощью словаря функций
Поделитесь результатами в комментариях!
А в следующей статье разберем, как комбинировать этот подход с декораторами для создания настоящих произведений искусства в коде.
Теги: Python
, Рефакторинг
, Паттерны проектирования
, Best Practices
, Код качество
Понравилась статья? Подписывайтесь на мой Telegram-канал Python Шпильки — там регулярно выходят такие же изящные решения для повседневных задач разработчика.