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

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

Практика 11. Расширения и протокол-ориентированное программирование

Цель: освоить расширения Swift (добавление методов, вычисляемых свойств, инициализаторов), реализации по умолчанию через расширения протоколов, условное соответствие (conditional conformance), стандартные протоколы (CustomStringConvertible, Equatable, Comparable, Hashable, Codable) и философию протокол-ориентированного программирования (POP). Все задания выполняются на Linux в командной строке.

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

  • Создавайте файлы с расширением .swift и запускайте командой swift file.swift.
  • Каждое задание можно оформлять в отдельном файле или объединять логически близкие в один.
  • Добавляйте print-вызовы для демонстрации работы каждого задания.
  • Обращайте внимание на то, как расширения и POP заменяют наследование — сравнивайте с подходом Python.

A. Разминка — расширения стандартных типов

  1. Расширьте тип Double, добавив:
    • Вычисляемое свойство km -> Double — конвертация километров в метры.
    • Вычисляемое свойство celsius -> String — форматирует число как температуру (например, "25.0°C").
    • Метод roundTo(places: Int) -> Double — округление до указанного количества знаков после запятой.

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

print(5.2.km) // 5200.0
print(36.6.celsius) // "36.6°C"
print(3.14159.roundTo(places: 2)) // 3.14
  1. Расширьте тип String, добавив:
    • Вычисляемое свойство wordCount: Int — количество слов (разделитель — пробел).
    • Метод truncate(to length: Int, trailing: String = "...") -> String — обрезает строку до указанной длины с добавлением суффикса.
    • Вычисляемое свойство isValidEmail: Bool — простая проверка наличия @ и . после @.

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

print("Hello world from Swift".wordCount) // 4
print("Длинная строка для теста".truncate(to: 10)) // "Длинная ст..."
print("user@mail.ru".isValidEmail) // true
print("invalid-email".isValidEmail) // false

B. Расширения протоколов — реализации по умолчанию

  1. Создайте протокол 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": "Алиса"} (собственный формат)
  1. Создайте протокол 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)

  1. Перепишите следующий 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. Стандартные протоколы

  1. Создайте структуру Student со свойствами id: Int, name: String, gpa: Double. Реализуйте соответствие следующим протоколам:
    • CustomStringConvertibledescription вида "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 отброшен)
  1. Создайте структуру Course со свойствами code: String, title: String, credits: Int. Реализуйте Equatable (по code), Hashable (по code) и CustomStringConvertible. Создайте Dictionary<Course, [Student]>, где ключ — курс, значение — список студентов.

E. Conditional Conformance и расширения с ограничениями

  1. Создайте обёртку struct Wrapper<T> с единственным свойством value: T.

Добавьте условные соответствия через расширения:

  • Wrapper: Equatable where T: Equatable
  • Wrapper: CustomStringConvertible where T: CustomStringConvertible
  • Wrapper: 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. Мини-проект

  1. «Конвейер обработки данных» (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
  1. Расширьте мини-проект: добавьте протокол 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 — напишите оба варианта и обсудите плюсы/минусы.