Практика 11. Практика к лекции 10 (часть 2)
Практика 11. Расширения и протокол-ориентированное программирование
Цель: освоить расширения Swift (добавление методов, вычисляемых свойств, инициализаторов), реализации по умолчанию через расширения протоколов, условное соответствие (conditional conformance), стандартные протоколы (CustomStringConvertible, Equatable, Comparable, Hashable, Codable) и философию протокол-ориентированного программирования (POP). Все задания выполняются на Linux в командной строке.
Рекомендации по выполнению:
- Создавайте файлы с расширением
.swiftи запускайте командойswift file.swift. - Каждое задание можно оформлять в отдельном файле или объединять логически близкие в один.
- Добавляйте
print-вызовы для демонстрации работы каждого задания. - Обращайте внимание на то, как расширения и POP заменяют наследование — сравнивайте с подходом Python.
A. Разминка — расширения стандартных типов
- Расширьте тип
Double, добавив:- Вычисляемое свойство
km -> Double— конвертация километров в метры. - Вычисляемое свойство
celsius -> String— форматирует число как температуру (например,"25.0°C"). - Метод
roundTo(places: Int) -> Double— округление до указанного количества знаков после запятой.
- Вычисляемое свойство
Пример ожидаемого поведения:
print(5.2.km) // 5200.0print(36.6.celsius) // "36.6°C"print(3.14159.roundTo(places: 2)) // 3.14- Расширьте тип
String, добавив:- Вычисляемое свойство
wordCount: Int— количество слов (разделитель — пробел). - Метод
truncate(to length: Int, trailing: String = "...") -> String— обрезает строку до указанной длины с добавлением суффикса. - Вычисляемое свойство
isValidEmail: Bool— простая проверка наличия@и.после@.
- Вычисляемое свойство
Пример ожидаемого поведения:
print("Hello world from Swift".wordCount) // 4print("Длинная строка для теста".truncate(to: 10)) // "Длинная ст..."print("user@mail.ru".isValidEmail) // trueprint("invalid-email".isValidEmail) // falseB. Расширения протоколов — реализации по умолчанию
- Создайте протокол
Serializableс методомtoJSON() -> String. Добавьте реализацию по умолчанию через расширение протокола, которая использует рефлексию (Mirror) для автоматической сериализации.
Реализуйте две структуры:
struct Config: Serializable— использует реализацию по умолчанию.struct User: Serializable— переопределяетtoJSON()с собственным форматом.
Пример ожидаемого поведения:
let config = Config(host: "localhost", port: 8080)print(config.toJSON())// {"host": "localhost", "port": "8080"}
let user = User(id: 1, name: "Алиса", email: "alice@example.com")print(user.toJSON())// {"user_id": 1, "username": "Алиса"} (собственный формат)- Создайте протокол
Loggableс методамиlogInfo(_ message: String),logWarning(_ message: String),logError(_ message: String). Через расширение протокола добавьте реализации по умолчанию, форматирующие вывод с префиксами[INFO],[WARN],[ERROR]и именем типа. Продемонстрируйте наstruct DatabaseService: Loggableиstruct NetworkService: Loggable.
Пример ожидаемого поведения:
let db = DatabaseService()db.logInfo("Подключение установлено")// [INFO] DatabaseService: Подключение установленоdb.logError("Таймаут соединения")// [ERROR] DatabaseService: Таймаут соединенияC. Перевод Python → Swift (протоколы вместо ABC)
- Перепишите следующий Python-код на Swift, используя протоколы, расширения протоколов (для
describe) и структуры:
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self) -> float: ...
@abstractmethod def perimeter(self) -> float: ...
def describe(self) -> str: return f"Площадь: {self.area():.2f}, Периметр: {self.perimeter():.2f}"
class Circle(Shape): def __init__(self, radius: float): self.radius = radius
def area(self) -> float: import math return math.pi * self.radius ** 2
def perimeter(self) -> float: import math return 2 * math.pi * self.radius
class Square(Shape): def __init__(self, side: float): self.side = side
def area(self) -> float: return self.side ** 2
def perimeter(self) -> float: return 4 * self.side
shapes = [Circle(5), Square(3)]for s in shapes: print(s.describe())Требования к Swift-версии:
- Протокол
Shapeс требованиямиarea() -> Doubleиperimeter() -> Double. - Реализация
describe() -> Stringчерез расширение протокола. - Структуры
CircleиSquare. - Добавьте
struct Triangle(по трём сторонам) — покажите, как легко расширять систему без изменения существующего кода. - Создайте массив
[Shape]и вызовитеdescribe()для каждого элемента.
D. Стандартные протоколы
- Создайте структуру
Studentсо свойствамиid: Int,name: String,gpa: Double. Реализуйте соответствие следующим протоколам:CustomStringConvertible—descriptionвида"Student(id: 1, name: Алиса, gpa: 4.5)".Equatable— сравнение поid.Comparable— сравнение поgpa(по убыванию: чем выше GPA, тем «меньше» в сортировке, чтобы отличники были первыми).Hashable— хеширование поid.
Продемонстрируйте:
- Вывод через
print(). - Сортировку массива
[Student]. - Использование
Set<Student>(добавление дубликатов поid). - Использование
Studentкак ключаDictionary.
Пример ожидаемого поведения:
let students = [ Student(id: 1, name: "Алиса", gpa: 4.5), Student(id: 2, name: "Борис", gpa: 3.8), Student(id: 3, name: "Вера", gpa: 4.9), Student(id: 1, name: "Алиса-дубль", gpa: 4.5) // дубликат по id]
let sorted = students.sorted()for s in sorted { print(s) }// Student(id: 3, name: Вера, gpa: 4.9)// Student(id: 1, name: Алиса, gpa: 4.5)// Student(id: 2, name: Борис, gpa: 3.8)
let uniqueSet = Set(students)print(uniqueSet.count) // 3 (дубликат по id отброшен)- Создайте структуру
Courseсо свойствамиcode: String,title: String,credits: Int. РеализуйтеEquatable(поcode),Hashable(поcode) иCustomStringConvertible. СоздайтеDictionary<Course, [Student]>, где ключ — курс, значение — список студентов.
E. Conditional Conformance и расширения с ограничениями
- Создайте обёртку
struct Wrapper<T>с единственным свойствомvalue: T.
Добавьте условные соответствия через расширения:
Wrapper: Equatable where T: EquatableWrapper: CustomStringConvertible where T: CustomStringConvertibleWrapper: Comparable where T: Comparable
Продемонстрируйте работу:
let a = Wrapper(value: 42)let b = Wrapper(value: 42)print(a == b) // true
let c = Wrapper(value: "hello")print(c.description) // "Wrapper(hello)"
let arr = [Wrapper(value: 3), Wrapper(value: 1), Wrapper(value: 2)]print(arr.sorted()) // [Wrapper(1), Wrapper(2), Wrapper(3)]F. Мини-проект
- «Конвейер обработки данных» (POP-подход):
Создайте систему обработки текста, основанную на протоколах:
- Протокол
TextProcessorс методомprocess(_ input: String) -> String. - Расширение протокола с методом
then(_ next: TextProcessor) -> ChainedProcessorдля цепочечной обработки. - Реализации:
TrimProcessor(удаление пробелов по краям),UppercaseProcessor,ReplaceProcessor(target: String, replacement: String),CensorProcessor(words: [String])(заменяет указанные слова звёздочками). struct ChainedProcessor: TextProcessor— последовательно применяет массив процессоров.struct Pipeline— принимаетTextProcessorи массив строк, возвращает обработанные строки.
Пример ожидаемого поведения:
let processor = ChainedProcessor(processors: [ TrimProcessor(), ReplaceProcessor(target: "мир", replacement: "Swift"), UppercaseProcessor()])
let texts = [" привет мир ", " hello мир "]let pipeline = Pipeline(processor: processor)let results = pipeline.run(texts)for r in results { print(r) }// ПРИВЕТ SWIFT// HELLO SWIFT- Расширьте мини-проект: добавьте протокол
TextAnalyzerс методомanalyze(_ input: String) -> [String: Int](статистика: количество слов, символов, предложений). Реализуйтеstruct BasicAnalyzer: TextAnalyzer. Интегрируйте анализатор вPipeline, чтобы после обработки выводилась статистика.
G. Критерии оценивания
- Корректность и полнота реализации: 0–5 баллов
- Грамотное использование расширений и реализаций по умолчанию: 0–4 балла
- Правильная реализация стандартных протоколов и conditional conformance: 0–4 балла
- Читаемость кода и наличие демонстрационных примеров: 0–3 балла
Максимум: 16 баллов. Бонус до +2 за дополнительные задания.
H. Дополнительно (по желанию)
- Реализуйте
Codableдля структуры с вложенными типами и покажите сериализацию/десериализацию в JSON (используйтеJSONEncoder/JSONDecoderиз Foundation). - Создайте расширение
Array where Element: Numericс методамиsum(),average(),median(). - Сравните подход POP (протокол + расширение + struct) с классическим ООП (class + наследование) для задания 9 — напишите оба варианта и обсудите плюсы/минусы.