Українська
Розбори та помилки проєктування
Власність колекції в предметному класі
Нехай клас групи студентів має внутрішній список імен. Повернення цього списку як List<String> приховує методи зміни, але залишає живий зв’язок із майбутніми оновленнями. Для звіту на конкретний момент це може бути неправильно: уже сформована відомість раптом змінить вміст після нового додавання.
Модель нижче повертає знімок. Внутрішній LinkedHashSet одночасно забезпечує унікальність і порядок вставлення. Контракт імен – непорожній рядок після обрізання пробілів; регістр має значення. Це рішення слід назвати явно, а не покладатися на випадкові особливості порівняння рядків.
kotlin
class StudyGroup {
private val names = linkedSetOf<String>()
fun enroll(rawName: String): Boolean {
val name = rawName.trim()
require(name.isNotEmpty()) { "empty name" }
return names.add(name)
}
fun snapshot(): List<String> = names.toList()
fun remove(name: String): Boolean = names.remove(name.trim())
}
fun main() {
val group = StudyGroup()
println(group.enroll("Ada"))
println(group.enroll(" Ada "))
val firstReport = group.snapshot()
group.enroll("Olena")
println(firstReport)
println(group.snapshot())
group.remove("Ada")
println(firstReport)
}text
true
false
[Ada]
[Ada, Olena]
[Ada]Знімок містить рядки, які самі є незмінними, тому в цьому прикладі копіювання елементів достатньо. Якщо замінити рядок на Student(var name: String), знімок структури не стане знімком стану студентів. Потрібно або зробити елементи незмінними, або повертати незалежні DTO з потрібними значеннями.
Клас також не розкриває конкретний LinkedHashSet у сигнатурі. За потреби реалізацію можна змінити, зберігши порядок і унікальність як зовнішній контракт. Якщо клієнт має залежність від конкретного класу колекції, така заміна буде значно складнішою.
Вибір структури на прикладі черги заявок
У черзі підтримки є ідентифікатор, час надходження та пріоритет. Якщо завжди обслуговують найстарішу заявку, ArrayDeque прямо виражає FIFO. Якщо завжди беруть найвищий пріоритет, потрібна черга пріоритетів. Якщо оператор часто шукає конкретну заявку за id, додатковий словник може бути кориснішим за багаторазовий лінійний обхід черги.
Кілька індексів створюють новий інваріант: заявка повинна бути узгоджено присутня або відсутня в кожній структурі. Передчасне ускладнення може спричинити більше помилок, ніж проста O(n) операція для кількох десятків записів. Тому обирають структуру за очікуваним обсягом і частотою операцій, а не лише за найкращою асимптотичною оцінкою.
Компаратор пріоритетів повинен визначати нічию. Можна порівнювати спочатку пріоритет, потім порядковий номер надходження. Не віднімайте довільні великі Int для реалізації порівняння: різниця може переповнитися. Використовуйте compareTo або композицію компараторів.
Якщо пріоритет елемента вже в PriorityQueue змінити на місці, структура не зобов’язана автоматично відновити порядок. Потрібно вилучити елемент і вставити з новим ключем або використати структуру з явно підтриманим оновленням пріоритету. Ця проблема подібна до зміни хешованого ключа: індекс залежить від стабільної властивості елемента.
Перевірки колекцій, що виявляють помилки проєктування
Перевірка порожнього набору показує, чи алгоритм випадково звертається до першого елемента. Один елемент перевіряє межі циклу, два однакові – політику дублів, а два різні з однаковим пріоритетом – правило нічиєї. Ці випадки зазвичай корисніші за великий випадковий набір без незалежного очікуваного результату.
Для масиву перевіряють перший і останній індекси, а також -1 і size. Для словника окремо перевіряють відсутній ключ та nullable-значення. Для множини використовують два різні екземпляри з рівним вмістом, щоб переконатися в правильності equals і hashCode, а не лише повторно додають те саме посилання.
Для захисного копіювання спочатку отримують результат, потім змінюють джерело й перевіряють старий результат. Якщо елементи змінювані, перевірку повторюють зі зміною внутрішнього поля. Так чітко видно, чи контракт обіцяє копію структури, чи глибший знімок предметного стану.
Перевірка порядку звіту повинна бути незалежною від порядку вставлення в хеш-таблицю. Додайте однакові записи в різній послідовності та порівняйте відсортовані звіти. Якщо порядок вставлення є вимогою, навпаки, переконайтеся, що він зберігся після заміни значення й після видалення з повторним додаванням.