Практика 2. Структуры, классы и копирование
Цель
Научиться осознанно выбирать между struct и class, прогнозировать результат
копирования, управлять изменяемостью значимых типов и находить неочевидное общее
состояние в смешанных моделях.
Общие требования
- каждое задание выполняйте в отдельном файле
task_N.swift; - добавляйте проверки
assert; - структуры, сравниваемые по данным, делайте
Equatable; - классы без предполагаемого наследования объявляйте
final; - после каждого задания кратко объясняйте выбор
structилиclass.
Задание 1. Значимый тип Vector2D
Реализуйте двумерный вектор как структуру.
Требования:
- свойства
xиyтипаDouble; - вычисляемое свойство
length; mutating func scale(by:), отклоняющий неположительный коэффициент;func scaled(by:)возвращает новую копию и не меняет исходную;- автоматическое сравнение через
Equatable.
struct Vector2D: Equatable { var x: Double var y: Double
var length: Double { fatalError("TODO") }
@discardableResult mutating func scale(by factor: Double) -> Bool { fatalError("TODO") }
func scaled(by factor: Double) -> Vector2D? { fatalError("TODO") }}Проверьте, что изменение копии не влияет на оригинал.
var original = Vector2D(x: 3, y: 4)var copy = originalcopy.scale(by: 2)
assert(original == Vector2D(x: 3, y: 4))assert(copy == Vector2D(x: 6, y: 8))assert(original.length == 5)Задание 2. Неизменяемые значения предметной области
Создайте структуры EmailAddress и StudentRecord.
struct EmailAddress: Equatable { let value: String
init?(_ value: String) { fatalError("TODO") }}
struct StudentRecord: Equatable { let id: Int let name: String let email: EmailAddress var grades: [Int]
var averageGrade: Double { fatalError("TODO") }
func addingGrade(_ grade: Int) -> StudentRecord? { fatalError("TODO") }}Требования:
- email непустой и содержит ровно одну
@, до и после неё есть символы; - оценки находятся в диапазоне
1...5; addingGrade(_:)возвращает изменённую копию либоnil;- исходная запись после вызова не меняется;
- среднее пустого списка равно
0.
Не превращайте StudentRecord в класс. Объясните, почему учебная запись в этом
задании удобна как снимок значения.
Задание 3. Ссылочный тип Playlist
Создайте плейлист, который может совместно использоваться несколькими частями программы.
final class Playlist { let id: Int var name: String private(set) var tracks: [String] = []
init(id: Int, name: String) { self.id = id self.name = name }
@discardableResult func add(_ track: String) -> Bool { fatalError("TODO") }
@discardableResult func remove(_ track: String) -> Bool { fatalError("TODO") }}Пустые названия и дубликаты добавлять нельзя. Покажите ссылочную семантику:
let playlist = Playlist(id: 1, name: "Учёба")let sharedPlaylist = playlist
playlist.add("Swift Basics")assert(sharedPlaylist.tracks == ["Swift Basics"])assert(playlist === sharedPlaylist)
let anotherPlaylist = Playlist(id: 1, name: "Учёба")assert(playlist !== anotherPlaylist)Письменно ответьте, почему одинаковые id и name не делают два экземпляра
тождественными.
Задание 4. Найдите скрытое общее состояние
Дан код:
final class Address { var city: String
init(city: String) { self.city = city }}
struct OrderDraft { var number: Int var address: Address}
let address = Address(city: "Нальчик")var first = OrderDraft(number: 1, address: address)var second = firstsecond.number = 2second.address.city = "Москва"Выполните три шага:
- До запуска предскажите значения
first.number,second.number,first.address.cityиsecond.address.city. - Подтвердите прогноз через
assert. - Переделайте
Addressв структуру так, чтобы черновики были полностью независимы. Добавьте тест, подтверждающий исправление.
Объясните, почему одной надписи struct у OrderDraft было недостаточно для
полностью независимого копирования модели.
Задание 5. Смешанная модель интернет-заказа
Спроектируйте модель из значимых и ссылочных типов.
import Foundation
struct Money: Equatable { let amount: Decimal let currency: String
init?(amount: Decimal, currency: String) { fatalError("TODO") }}
struct Product: Equatable { let id: UUID let name: String let price: Money
init?(id: UUID = UUID(), name: String, price: Money) { fatalError("TODO") }}
struct OrderItem: Equatable { let product: Product var quantity: Int
init?(product: Product, quantity: Int) { fatalError("TODO") }
var subtotal: Decimal { fatalError("TODO") }}
final class ShoppingCart { let id: UUID private(set) var items: [OrderItem] = []
var total: Decimal { fatalError("TODO") }
init(id: UUID = UUID()) { self.id = id }
@discardableResult func add(_ product: Product, quantity: Int = 1) -> Bool { fatalError("TODO") }
@discardableResult func remove(productID: UUID) -> Bool { fatalError("TODO") }}Инварианты:
- сумма денег неотрицательна, код валюты непустой;
- имя товара непустое;
- количество положительное;
- повторное добавление товара увеличивает количество существующей позиции;
totalвсегда вычисляется из текущих позиций.
В конце создайте две ссылки на одну корзину и докажите через === и assert,
что они видят общий список. Затем создайте вторую корзину с такими же товарами и
покажите, что это другой экземпляр.
Задание 6. Эксперимент с Array и copy-on-write
Создайте массив из миллиона целых чисел, присвойте его второй переменной и измените только второй массив.
var firstArray = Array(0..<1_000_000)var secondArray = firstArraysecondArray.append(1_000_000)
assert(firstArray.count == 1_000_000)assert(secondArray.count == 1_000_001)Ответьте письменно:
- Почему первый массив не изменился?
- Обязан ли Swift физически копировать миллион элементов в момент присваивания?
- В какой момент реализация может создать отдельный буфер?
- Почему copy-on-write не превращает
Arrayв ссылочный тип?
Критерии оценки
| Раздел | Доля |
|---|---|
| Задания 1–2: значимые типы и безопасное копирование | 25% |
| Задание 3: ссылочная семантика и идентичность | 15% |
| Задание 4: анализ смешанной модели | 20% |
| Задание 5: проектирование модели заказа | 25% |
| Задание 6 и качество объяснений | 15% |
Работа засчитывается при наборе не менее 60%.
Вопросы для самопроверки
- Почему изменяющий метод структуры требует
mutating? - Чем копирование структуры отличается от присваивания класса?
- Как
letвлияет на структуру и на ссылку на класс? - Может ли структура содержать класс? Как это влияет на копирование?
- Чем
==отличается от===? - Почему
Arrayостаётся значимым типом при copy-on-write? - Какие сущности модели заказа получили тип
structи почему? - Почему корзина реализована классом?