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

Практика 10. Практика к лекции 10 (часть 1)

Практика 10. Протоколы

Цель: освоить объявление и использование протоколов Swift (требования к свойствам и методам, conformance для struct/class/enum, протокол как тип, композиция протоколов, class-only протоколы) через серию упражнений от базовых к проектным. Все задания выполняются на Linux в командной строке.

Рекомендации по выполнению:

  • Создавайте файлы с расширением .swift и запускайте командой swift file.swift.
  • Каждое задание можно оформлять в отдельном файле или объединять логически близкие в один.
  • Добавляйте print-вызовы для демонстрации работы каждого задания.
  • Сравнивайте протоколы Swift с абстрактными классами (ABC) и утиной типизацией Python.

A. Разминка

  1. Создайте протокол Describable с единственным требованием — вычисляемое свойство description: String { get }. Реализуйте соответствие для:
    • struct Planet (свойства: name: String, distanceFromSun: Double)
    • enum Season (варианты: spring, summer, autumn, winter)

Создайте массив [Describable] и выведите описание каждого элемента.

Пример ожидаемого поведения:

let items: [Describable] = [
Planet(name: "Земля", distanceFromSun: 149.6),
Season.winter,
Planet(name: "Марс", distanceFromSun: 227.9)
]
for item in items {
print(item.description)
}
// Планета Земля (149.6 млн км от Солнца)
// Сезон: зима
// Планета Марс (227.9 млн км от Солнца)
  1. Создайте протокол Togglable с требованием mutating func toggle(). Реализуйте соответствие для:
    • enum OnOffSwitch (варианты: on, off) — переключает между состояниями.
    • struct LightDimmer (свойство brightness: Int, диапазон 0–100) — переключает между 0 и 100.

B. Протокол с требованиями к свойствам и методам

  1. Создайте протокол Payable с требованиями:
    • свойство salary: Double { get }
    • метод monthlyPay() -> Double

Реализуйте:

  • struct FullTimeEmployee — имя, годовая зарплата. Ежемесячная выплата = salary / 12.
  • struct Contractor — имя, дневная ставка, количество отработанных дней. Ежемесячная выплата = salary * дни.

Создайте массив [Payable], выведите имя и месячную зарплату каждого. Напишите функцию totalMonthlyPayroll(_ employees: [Payable]) -> Double.

Пример ожидаемого поведения:

let team: [Payable] = [
FullTimeEmployee(name: "Алиса", salary: 1_200_000),
Contractor(name: "Борис", dailyRate: 5_000, daysWorked: 20)
]
for member in team {
print("\(member.monthlyPay())")
}
// 100000.0
// 100000.0
print(totalMonthlyPayroll(team)) // 200000.0

C. Протокол как тип и стек

  1. Создайте протокол Stackable с методами:
    • mutating func push(_ item: Int)
    • mutating func pop() -> Int?
    • func peek() -> Int?
    • var isEmpty: Bool { get }

Реализуйте struct IntStack, соответствующий Stackable, используя внутренний массив [Int]. Добавьте вычисляемое свойство count: Int.

Пример ожидаемого поведения:

var stack = IntStack()
stack.push(10)
stack.push(20)
stack.push(30)
print(stack.peek()!) // 30
print(stack.pop()!) // 30
print(stack.count) // 2
print(stack.isEmpty) // false
  1. Продемонстрируйте использование протокола как типа: создайте функцию fillAndEmpty(_ stack: inout IntStack), которая добавляет 5 элементов, а затем извлекает все, печатая каждый.

D. Композиция протоколов

  1. Создайте три протокола:
    • Named — свойство name: String { get }
    • Aged — свойство age: Int { get }
    • Greetable — метод greet() -> String

Реализуйте struct Person, соответствующий всем трём. Напишите функцию, принимающую параметр типа Named & Aged & Greetable и выводящую приветствие.

Пример ожидаемого поведения:

func introduce(_ someone: Named & Aged & Greetable) {
print("\(someone.greet()) Мне \(someone.age) лет.")
}
let person = Person(name: "Анна", age: 22)
introduce(person) // Привет, я Анна! Мне 22 лет.
  1. Создайте протоколы Readable (метод read() -> String) и Writable (метод write(_ data: String)). Создайте тип FileHandle, соответствующий обоим. Напишите функцию copyContent(from source: Readable, to destination: inout Writable).

E. Class-only протоколы и наследование протоколов

  1. Создайте протокол Cacheable: AnyObject (доступен только для классов) с методом clearCache(). Реализуйте class ImageCache и class DataCache. Продемонстрируйте, что структура не может соответствовать этому протоколу (оставьте закомментированный пример с объяснением).

Создайте наследующий протокол PersistentCacheable: Cacheable с дополнительным методом saveToDisk(path: String). Реализуйте class DiskImageCache, соответствующий PersistentCacheable.

Пример ожидаемого поведения:

let caches: [Cacheable] = [ImageCache(), DataCache(), DiskImageCache()]
for cache in caches {
cache.clearCache()
}
// ImageCache: кэш очищен
// DataCache: кэш очищен
// DiskImageCache: кэш очищен
if let persistent = caches[2] as? PersistentCacheable {
persistent.saveToDisk(path: "/tmp/cache")
}

F. Мини-проект

  1. «Система уведомлений» — создайте протокол-ориентированную систему:

Протоколы:

  • Notification — свойства title: String, message: String, priority: Priority (enum: low, medium, high).
  • NotificationSender — метод send(_ notification: Notification).
  • NotificationFilter — метод shouldSend(_ notification: Notification) -> Bool.

Реализуйте:

  • struct EmailNotification: Notification (с дополнительным свойством recipient: String).
  • struct PushNotification: Notification.
  • struct ConsoleSender: NotificationSender — печатает уведомление в консоль.
  • struct PriorityFilter: NotificationFilter — пропускает только уведомления с приоритетом >= medium.
  • struct NotificationCenter — содержит массив senders и filters, метод dispatch(_ notification: Notification) проверяет все фильтры и отправляет через все отправители.

Пример ожидаемого поведения:

var center = NotificationCenter(
senders: [ConsoleSender()],
filters: [PriorityFilter()]
)
center.dispatch(EmailNotification(
title: "Сервер",
message: "Диск заполнен на 95%",
priority: .high,
recipient: "admin@example.com"
))
// [HIGH] Сервер: Диск заполнен на 95%
center.dispatch(PushNotification(
title: "Обновление",
message: "Доступна новая версия",
priority: .low
))
// (не отправлено — низкий приоритет)
  1. Добавьте к мини-проекту протокол NotificationLogger с методом log(_ notification: Notification). Реализуйте struct InMemoryLogger, хранящий массив отправленных уведомлений. Интегрируйте логгер в NotificationCenter.

G. Критерии оценивания

  • Корректность и полнота реализации: 0–5 баллов
  • Грамотное определение и использование протоколов: 0–4 балла
  • Применение композиции протоколов и протоколов как типов: 0–4 балла
  • Читаемость кода и наличие демонстрационных примеров: 0–3 балла

Максимум: 16 баллов. Бонус до +2 за дополнительные задания.


H. Дополнительно (по желанию)

  • Добавьте к IntStack соответствие протоколу Sequence (реализуйте makeIterator()), чтобы можно было использовать стек в for-in.
  • Создайте протокол Validatable с методом validate() -> [String] (список ошибок). Реализуйте для структур RegistrationForm и PaymentForm.
  • Сравните подход Swift (протоколы + struct) с Python (ABC + class) — напишите оба варианта для задания 3 и обсудите различия.