Практика 10. Практика к лекции 10 (часть 1)
Практика 10. Протоколы
Цель: освоить объявление и использование протоколов Swift (требования к свойствам и методам, conformance для struct/class/enum, протокол как тип, композиция протоколов, class-only протоколы) через серию упражнений от базовых к проектным. Все задания выполняются на Linux в командной строке.
Рекомендации по выполнению:
- Создавайте файлы с расширением
.swiftи запускайте командойswift file.swift. - Каждое задание можно оформлять в отдельном файле или объединять логически близкие в один.
- Добавляйте
print-вызовы для демонстрации работы каждого задания. - Сравнивайте протоколы Swift с абстрактными классами (
ABC) и утиной типизацией Python.
A. Разминка
- Создайте протокол
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 млн км от Солнца)- Создайте протокол
Togglableс требованиемmutating func toggle(). Реализуйте соответствие для:enum OnOffSwitch(варианты:on,off) — переключает между состояниями.struct LightDimmer(свойствоbrightness: Int, диапазон 0–100) — переключает между 0 и 100.
B. Протокол с требованиями к свойствам и методам
- Создайте протокол
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.0print(totalMonthlyPayroll(team)) // 200000.0C. Протокол как тип и стек
- Создайте протокол
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()!) // 30print(stack.pop()!) // 30print(stack.count) // 2print(stack.isEmpty) // false- Продемонстрируйте использование протокола как типа: создайте функцию
fillAndEmpty(_ stack: inout IntStack), которая добавляет 5 элементов, а затем извлекает все, печатая каждый.
D. Композиция протоколов
- Создайте три протокола:
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 лет.- Создайте протоколы
Readable(методread() -> String) иWritable(методwrite(_ data: String)). Создайте типFileHandle, соответствующий обоим. Напишите функциюcopyContent(from source: Readable, to destination: inout Writable).
E. Class-only протоколы и наследование протоколов
- Создайте протокол
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. Мини-проект
- «Система уведомлений» — создайте протокол-ориентированную систему:
Протоколы:
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))// (не отправлено — низкий приоритет)- Добавьте к мини-проекту протокол
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 и обсудите различия.