Шаг 2: способы доставки за общим интерфейсом
Каждая ветка
switchстановится своим классом с методомcost, а калькулятор только выбирает, кому отдать посылку.
Урок: От switch к стратегии. Предыдущий шаг: одна функция на все способы. Следующий: реестр тарифов и правила из конфига.
Что было
Одна функция со switch по способу доставки и if по стране внутри каждой ветки. Цены, правила по странам и выбор способа жили в одном месте, и любая правка трогала всё сразу.
Что меняем
Делаем самый механический и самый безопасный ход: вырезаем каждую ветку в отдельный класс. Появляется контракт ShippingMethod с одним методом cost(Parcel), и три реализации: PickupPoint, Courier, Post. Код внутри них почти дословно тот, что был в ветках, только if по стране у курьера стал своим маленьким match.
Расчёт начатого килограмма переехал в Parcel::kilograms(): это знание о посылке, а не о способе доставки, и теперь его не нужно повторять.
ShippingCalculator::cost стал двумя строками: methodFor по коду способа возвращает нужный объект, а тот считает цену. Выбор всё ещё switch (в PHP match), и это сделано намеренно: за один шаг меняем одну вещь. Сначала разделили расчёт по классам, выбор трогаем на следующем шаге.
Тесты те же, что на шаге 1: те же шесть посылок с ценами и те же три отказа. В Go изменилась только строка вызова: функция ShippingCost стала методом Calculator.Cost. Тесты зелёные, значит поведение не поменялось.
Как это собирается
Сборка та же, что на шаге 1: калькулятор создаётся без аргументов, все способы доставки он создаёт сам внутри methodFor. Корзина спрашивает цену по каждому способу, express по-прежнему "not available".
Сценарий целиком с выводом: пример на PHP, он же на Go, Python и TypeScript в панели кода.
Что стало лучше
- Каждый способ доставки читается и тестируется отдельно:
(new Courier())->cost(...)не зависит от почты и пункта выдачи. - Правка тарифа курьера трогает только класс
Courier. Остальные способы не открываются. - У стратегии есть имя. В разговоре "поправь курьера" теперь указывает на конкретный файл и класс, а не на строки 40-55.
- Появился контракт
ShippingMethod. Это шов, через который дальше пойдёт весь рефакторинг: кто угодно, кто умеетcost(Parcel), может стать способом доставки.
Что ещё болит
- Добавить
express- всё ещё правкаmethodFor. Калькулятор знает все способы поимённо и создаёт их сам, поэтому подменить способ в тесте или в другой стране нельзя. - Доступность по странам размазана по классам: пункт выдачи проверяет "только RU", курьер держит свой список стран. Ответ на вопрос "что вообще доступно в Беларуси" надо собирать по трём файлам.
- Цены всё так же зашиты в код.
CourierиPostпри этом делают одно и то же: база плюс цена за килограмм, только с разными числами. - Каждый вызов
costсоздаёт новый объект способа. Здесь это дёшево, но сигнализирует о том же: объектами управляет не тот, кто должен.
Запуск: make test T=lessons/001-refactor-to-strategy/step-2