Практика 5. Протоколы и сменяемые зависимости
Цель
Спроектировать сервис, который зависит от небольших протоколов, и доказать сменяемость реализаций без наследования и приведения типов.
Структура проекта
oop-swift-practice-05/├── README.md└── Sources/ └── main.swiftПредметная область
Сервис оформляет заказ: проверяет данные, сохраняет заказ и отправляет уведомление. Реальных баз данных и сетевых запросов не требуется.
Задание 1. Модель
struct Order: Equatable { let id: Int let email: String let total: Decimal}Определите инварианты модели и создайте init?, запрещающий неположительный id,
пустой email и отрицательную сумму.
Задание 2. Контракты
Объявите протоколы:
protocol OrderValidating { func validate(_ order: Order) -> Bool}
protocol OrderSaving { func save(_ order: Order) -> Bool}
protocol OrderNotifying { func sendConfirmation(for order: Order)}Объясните, почему это три протокола, а не один OrderManager.
Задание 3. Реализации в памяти
Создайте:
DefaultOrderValidator;MemoryOrderRepository, сохраняющий заказы в массиве;MemoryOrderNotifier, сохраняющий идентификаторы уведомлённых заказов.
Откройте наружу состояние только для чтения через private(set).
Задание 4. Сервис
final class CheckoutService { private let validator: any OrderValidating private let repository: any OrderSaving private let notifier: any OrderNotifying
init( validator: any OrderValidating, repository: any OrderSaving, notifier: any OrderNotifying ) { fatalError("TODO") }
func checkout(_ order: Order) -> Bool { fatalError("TODO") }}Алгоритм:
- отклонить заказ, не прошедший проверку;
- попытаться сохранить заказ;
- отправить уведомление только после успешного сохранения;
- вернуть итог операции.
Сервис не должен создавать конкретные зависимости внутри себя.
Задание 5. Подмены для проверки
Реализуйте управляемые подмены:
final class StubValidator: OrderValidating { let result: Bool init(result: Bool) { self.result = result } func validate(_ order: Order) -> Bool { result }}Добавьте аналогичную подмену репозитория и spy-уведомитель, считающий вызовы. Проверьте не менее четырёх сценариев:
- валидация не пройдена;
- сохранение завершилось ошибкой;
- успешное оформление;
- повторное оформление того же заказа по правилам выбранной реализации.
Задание 6. Композиция протоколов
Создайте функцию диагностики, принимающую объект, который одновременно умеет
сохранять заказ и предоставлять читаемое описание состояния. Введите отдельный
небольшой протокол для описания и используйте композицию any A & B.
Дополнительное задание
Создайте ConsoleOrderNotifier, не меняя CheckoutService. Покажите в README,
какую часть программы пришлось изменить для выбора другой реализации.
Требования к сдаче
- ссылка на Git-репозиторий;
- Swift 6, запуск описан в README;
- минимум три узких протокола и две реализации хотя бы одного контракта;
- минимум 15 проверок
assert; - зависимости передаются через инициализатор;
- отсутствуют глобальное изменяемое состояние и
fatalError("TODO").
Критерии оценки
| Раздел | Доля |
|---|---|
| Модель и инварианты | 15% |
| Качество протоколов | 20% |
| Реализации зависимостей | 20% |
| Сервис и порядок операций | 25% |
| Подмены, проверки и README | 20% |
Вопросы для защиты
- Почему сервис хранит зависимости как протокольные типы?
- Что означает
any OrderSaving? - Почему протоколы разделены?
- Можно ли выполнить
{ get }изменяемым свойством? - Когда метод протокола должен быть
mutating? - Чем stub отличается от spy в этой работе?
- Почему уведомление отправляется после сохранения?
- Что пришлось изменить при добавлении консольного уведомителя?
- Как протоколы помогают тестировать сервис?
- Когда отдельный протокол был бы избыточен?