Практика 6. Расширения и композиция поведения
Цель
Применить расширения для организации API, реализовать поведение по умолчанию в protocol extensions и заменить разрастающуюся иерархию композицией стратегий.
Структура проекта
oop-swift-practice-06/├── README.md└── Sources/ └── main.swiftПредметная область
Нужно рассчитать итоговую стоимость заказа. На цену независимо влияют скидка и стоимость доставки. Решение должно позволять комбинировать стратегии без создания подкласса для каждого сочетания.
Задание 1. Доменные модели
struct CartItem: Equatable { let name: String let unitPrice: Decimal let quantity: Int}
struct Cart { private(set) var items: [CartItem] = []}Добавьте проверяемые способы создания. Цена неотрицательная, количество положительное, название после удаления пробелов не пустое.
Задание 2. Расширения модели
В отдельных extensions реализуйте:
CartItem.subtotalкак вычисляемое свойство;Cart.add(_:);Cart.subtotal;- соответствие
CartItemпротоколуCustomStringConvertible.
Не добавляйте глобальные функции и не дублируйте формулу стоимости.
Задание 3. Ограниченное расширение
Добавьте коллекциям элементов CartItem вычисляемую общую стоимость.
extension Collection where Element == CartItem { var total: Decimal { fatalError("TODO") }}Проверьте работу для массива и для ArraySlice<CartItem>.
Задание 4. Стратегии скидки
protocol DiscountPolicy { func discount(for subtotal: Decimal) -> Decimal}
extension DiscountPolicy { func finalPrice(for subtotal: Decimal) -> Decimal { max(0, subtotal - discount(for: subtotal)) }}Реализуйте:
NoDiscount;PercentDiscountс проверкой диапазона0...100;ThresholdDiscount, предоставляющий фиксированную скидку после достижения порога.
Задание 5. Стратегии доставки
Создайте протокол ShippingPolicy с методом расчёта доставки. Реализуйте:
- бесплатную доставку;
- фиксированную стоимость;
- бесплатную доставку после достижения заданной суммы.
Все входные параметры должны сохранять корректные инварианты.
Задание 6. Композиция
struct CheckoutCalculator { let discountPolicy: any DiscountPolicy let shippingPolicy: any ShippingPolicy
func total(for cart: Cart) -> Decimal { fatalError("TODO") }}Алгоритм:
- получить промежуточную стоимость корзины;
- применить скидку;
- рассчитать доставку по цене после скидки;
- вернуть сумму цены после скидки и доставки.
Создайте не менее шести комбинаций стратегий. Для добавления новой комбинации нельзя создавать новый класс калькулятора.
Задание 7. Требование и метод extension
Добавьте в DiscountPolicy требование label. Предоставьте значение по умолчанию в
extension, затем переопределите его в одной стратегии. Покажите вызов через
any DiscountPolicy.
После этого создайте дополнительный метод только в extension и экспериментально сравните диспетчеризацию. Вывод объясните в README.
Дополнительное задание
Создайте OrderSummary и добавьте ему условное соответствие Equatable, если все
содержащиеся значения сравнимы. Либо реализуйте generic-обёртку Box<Value> с
условными соответствиями Equatable и CustomStringConvertible.
Требования к сдаче
- ссылка на Git-репозиторий;
- Swift 6, команды запуска приведены в README;
- расширения сгруппированы по ответственности;
- отсутствуют хранимые свойства в extensions;
- минимум по три стратегии скидки и доставки;
- минимум 20 проверок
assert; - отсутствуют
fatalError("TODO"),as!и выбор поведения по строковому типу.
Критерии оценки
| Раздел | Доля |
|---|---|
| Модели и инварианты | 15% |
| Расширения и ограниченный API | 20% |
| Стратегии скидки | 15% |
| Стратегии доставки | 15% |
| Композиция и комбинации | 25% |
| Проверки и объяснение диспетчеризации | 10% |
Вопросы для защиты
- Почему
subtotalявляется вычисляемым свойством? - Почему extension не может добавить хранимое свойство?
- Когда ограниченное расширение доступно коллекции?
- Что предоставляет protocol extension?
- Почему
labelобъявлен требованием протокола? - Как выбирается реализация метода, существующего только в extension?
- Почему скидка и доставка представлены отдельными стратегиями?
- Сколько подклассов понадобилось бы для всех комбинаций поведения?
- Как добавить новую стратегию, не изменяя калькулятор?
- Когда наследование было бы уместнее композиции?