handbookНаписать

lessons/001-refactor-to-strategy/step-3/README.md · тема · 4 языка · 43 теста

Шаг 3: реестр тарифов и правила из конфига ​

Тарифы регистрируются по коду, а какой тариф у какого способа в какой стране - решает список правил из конфига.

Урок: От switch к стратегии. Предыдущий шаг: способы доставки за общим интерфейсом.

Что было ​

Три класса за контрактом ShippingMethod, но калькулятор выбирал их через match по коду и создавал сам. Доступность по странам жила внутри классов, цены были зашиты в код, а Courier и Post делали одно и то же с разными числами.

Что меняем ​

Разводим два вопроса, которые на шаге 2 ещё сидели в одних классах.

Сколько стоит - это стратегия, и теперь она ничего не знает ни о способах, ни о странах. Контракт переименован в Tariff: так честнее, он отвечает только за цену. Вместо трёх классов под конкретные способы осталось три класса под форму тарифа: FlatRate (фиксированная цена), PerKilogram (база плюс за начатый килограмм) и FreeOver, который оборачивает другой тариф и обнуляет цену от порога. Пункт выдачи теперь FreeOver(300000, FlatRate(19900)), курьер по России PerKilogram(39000, 5000).

Что доступно - это данные. Rule - одна строка конфига: способ, список стран (нет списка - любая страна) и код тарифа. ShippingCalculator идёт по правилам сверху вниз, первое подошедшее называет код, TariffRegistry выдаёт по коду тариф. Ни одно правило не подошло - UnsupportedShipping, как и раньше.

Калькулятор проверяет правила при создании: если правило ссылается на код, которого нет в реестре, он падает сразу при старте, а не на первом заказе в выходные. Это единственный новый тест урока: конфиг - новый вход, и у него появилась своя ошибка.

Остальные тесты те же самые: шесть цен и три отказа. Поменялась только строка, которая создаёт калькулятор: теперь в тесте есть список правил и регистрация тарифов, как в настоящем модуле сборки.

Как это собирается ​

Сборка - главное, что изменилось. Правила читаются из конфига (в примере - литерал, в приложении shipping.yaml или таблица), тарифы регистрируются кодом один раз при старте, ShippingCalculator::fromConfig связывает одно с другим. Корзина зовёт cost так же, как на шагах 1 и 2.

Экспресс-доставка, которую маркетинг просил с первого шага, появилась без правки калькулятора: одна строка в правилах (express, только RU, тариф express-ru) и одна регистрация FlatRate(99000). В выводе express больше не "not available".

Сценарий целиком с выводом: пример на PHP, он же на Go, Python и TypeScript в панели кода.

Что стало лучше ​

  • Новый способ доставки или новая страна - строка в конфиге. Калькулятор, реестр и тарифы не открываются.
  • Ответ на вопрос "что доступно в Беларуси" - в одном месте, в списке правил, а не в трёх классах.
  • Тарифов три формы на любое число способов. Courier и Post больше не дублируют друг друга, а "бесплатно от порога" можно навесить на любой тариф.
  • Калькулятор ничего не создаёт сам: правила и тарифы приходят снаружи, поэтому в тесте можно подставить любые.
  • Опечатка в коде тарифа ловится при старте приложения.

Итог ​

Код прошёл путь от switch к стратегии за два шага, и на каждом тесты оставались зелёными без правки самих проверок. Порядок важен: сначала разделили расчёт по классам, не трогая выбор (шаг 2), потом вынесли выбор в данные (шаг 3). Если сделать оба хода сразу, при красном тесте непонятно, что именно сломалось.

Где остановиться:

  • Если способы и тарифы меняются раз в год, шага 2 достаточно. Конфиг и реестр окупаются, когда правки идут постоянно.
  • Цены всё ещё в коде регистрации. Если их меняют чаще релизов, следующий ход - параметры тарифа тоже в конфиг, а реестр хранит фабрики по типу (flat, per_kg), а не готовые объекты.
  • Порядок правил решает исход: подходит первое. Пока правил десяток, это читается. Если их сотни и условия сложнее страны (вес, габарит, дни недели), правила становятся спецификациями или уходят в отдельный движок.

Про сам паттерн: Стратегия.

Запуск: make test T=lessons/001-refactor-to-strategy/step-3

© 2014-2026 Тимур ТурдыевСайт и справочник работают на VPS от AdminVPS