Перейти к содержимому

Практика 4. Наследование и полиморфизм

Цель

Спроектировать небольшую иерархию классов, переопределить поведение и обработать разнородную коллекцию через единый базовый тип.

Организация проекта

oop-swift-practice-04/
├── README.md
└── Sources/
└── main.swift

В README.md укажите ФИО, группу, команды запуска и ответы на вопросы.

Предметная область

Система рассчитывает стоимость доставки разными способами. Все варианты имеют идентификатор, базовую стоимость и операцию расчёта итоговой цены.

Задание 1. Базовый класс

Реализуйте класс Delivery.

class Delivery {
let id: Int
let basePrice: Decimal
init?(id: Int, basePrice: Decimal) {
fatalError("TODO")
}
func price(for weight: Decimal) -> Decimal {
fatalError("TODO")
}
var description: String {
fatalError("TODO")
}
}

Требования:

  • id положительный;
  • цена неотрицательная;
  • отрицательный вес возвращает базовую цену без дополнительной стоимости;
  • описание содержит идентификатор и тип доставки.

Задание 2. Подклассы

Создайте три класса:

  • CourierDelivery: доплата 20 за каждый килограмм;
  • PickupDelivery: итог всегда равен базовой цене;
  • ExpressDelivery: доплата 40 за килограмм и фиксированная надбавка 300.

Переопределите price(for:) и description. Решите, где нужен super, и объясните решение в README. Классы, дальнейшая специализация которых не нужна, объявите final.

Задание 3. Полиморфная коллекция

func printSummary(_ deliveries: [Delivery], weight: Decimal) {
for delivery in deliveries {
print("\(delivery.description): \(delivery.price(for: weight))")
}
}

Создайте коллекцию [Delivery] из объектов всех трёх подклассов. Докажите проверками assert, что вызываются разные реализации одного метода.

Задание 4. Общий алгоритм

Реализуйте функции:

func cheapest(_ deliveries: [Delivery], weight: Decimal) -> Delivery?
func totalPrice(_ deliveries: [Delivery], weight: Decimal) -> Decimal

Запрещено:

  • использовать switch или цепочку if по конкретным типам;
  • хранить название типа и выбирать формулу по строке;
  • дублировать расчёт стоимости вне классов.

Задание 5. Проверка типов

Подсчитайте количество экспресс-доставок через is. Затем безопасно получите первую ExpressDelivery через as?.

Объясните, почему основной расчёт цены не должен зависеть от приведения типа. Не используйте as!.

Задание 6. Проверка подстановки

Напишите общий набор проверок базового контракта:

func checkDeliveryContract(_ delivery: Delivery) {
assert(delivery.price(for: -1) >= 0)
assert(delivery.price(for: 0) >= 0)
assert(!delivery.description.isEmpty)
}

Выполните его для каждого подкласса. Добавьте ещё не менее трёх проверок контракта.

Дополнительное задание

Создайте ошибочный подкласс, нарушающий одно обещание базового класса. Опишите, какой клиентский код он ломает, затем исправьте модель. В итоговой версии нарушающий класс оставлять не нужно.

Требования к сдаче

  • ссылка на Git-репозиторий;
  • код компилируется Swift 6;
  • не менее 15 содержательных assert;
  • отсутствуют fatalError("TODO") и as!;
  • история содержит осмысленные коммиты;
  • README объясняет решения, а не только повторяет условие.

Критерии оценки

РазделДоля
Базовый класс и инварианты20%
Подклассы и переопределение25%
Полиморфные алгоритмы25%
Проверка типов и подстановки15%
Проверки, README и качество кода15%

Вопросы для защиты

  1. Почему массив имеет тип [Delivery], но вызывает методы подклассов?
  2. Чем override отличается от перегрузки?
  3. В каком случае полезен super?
  4. Почему подклассы объявлены final?
  5. Что сломается, если подкласс вернёт отрицательную цену?
  6. Почему расчёт через switch по типам хуже полиморфного вызова?
  7. Чем is отличается от as??
  8. Почему as! нежелателен в этой задаче?
  9. Как понять, что отношение наследования выбрано корректно?
  10. Как эту модель можно было бы построить через композицию?