Українська
Розбори та типові помилки
Аудит інтерфейсу перед реалізацією
Почніть із клієнтського сценарію. Клієнту лампи може бути потрібне лише setOn, а клієнту розкладу – schedule. Один великий інтерфейс із десятками методів примушує прості пристрої реалізовувати беззмістовні заглушки. Краще відокремити справжні незалежні можливості й передавати потрібну роль.
Для кожної операції запишіть передумови, результат, можливі винятки й стан після відмови. Наприклад, яскравість 0–100 не означає автоматично, що нуль вимикає пристрій: це окреме правило. У прикладі ввімкнення та яскравість зберігаються незалежно, а effective повертає нуль для вимкненої лампи.
Для стратегії порівняйте однакові вхідні суми, для спостерігача – однакову подію до та після відписки, для команди – стан до execute, після execute, undo й redo. Тести мають відображати користувацький контракт, а не тільки викликати всі методи.
Якщо для пояснення одного об’єкта потрібно простежити багато ромбів, перевірте, чи не змішано ролі та володіння. Додатковий рівень абстракції має спрощувати клієнта. Якщо він лише приховує простий список під складною ієрархією, композиція буде зрозумілішою.
Перевірка абстракцій і типові відмови
Спроба створити абстрактний тип має завершуватися діагностикою компілятора. Якщо похідний тип теж абстрактний, перевірте, чи він реалізував усі чисто віртуальні сигнатури, включно з const. Помилка не усувається випадковим додаванням конструктора.
Для ромба перевірте адреси Person, отримані через Student й Employee, використовуючи коректні мовні cast. За віртуальної бази вони мають позначати один підоб’єкт. Це безпечна перевірка ідентичності, яка не читає приховані байти й не залежить від конкретних зміщень ABI.
Для множинних інтерфейсів викличте можливості через кожне базове посилання. Для колекцій власників перевірте завершення життя. Для невласних зв’язків зафіксуйте порядок створення й знищення та заборонені сценарії, наприклад callback після знищення слухача.
У звіті відокремте те, що перевірив компілятор, від того, що перевірив запуск. Коректний override не доводить предметної правильності знижки; правильна сума не доводить відсутності недійсного вказівника в підписках. Надійна реалізація потребує обох видів перевірки та ясного контракту.
Межі композиції на прикладі стратегій знижки
Замовлення не повинно питати стратегію про її конкретний клас, щоб обчислити суму. Якщо після кожного додавання політики доводиться розширювати switch у Order, абстракція не виконала свою роботу. Контекст має залежати від операції apply та її передумов, а не від назв реалізацій.
Разом із тим надто загальний інтерфейс може приховувати потрібні відмінності. Якщо одна політика потребує кількості товарів, а інша – членства покупця, одного числа cents вже недостатньо. Слід передати чітко описаний набір вхідних даних або відокремити різні сценарії. Глобальні змінні не є хорошим способом доповнити прихований контекст.
Для тестування використовуйте невелику контрольну політику з передбачуваним результатом. Тоді можна перевірити, що Order справді делегує виклик і не застосовує власну дубльовану формулу. Окремі тести конкретної процентної політики перевіряють арифметику та округлення. Це різні відповідальності.
Що відбувається з подіями під час відписки
Нехай станція обходить слухачів A, B і C. Якщо A під час callback видалить B із того самого vector, позиції елементів можуть змінитися, а ітератор циклу стане недійсним. Навіть якщо програма випадково не впаде, контракт того, хто отримає поточну подію, лишається неясним.
Є кілька осмислених політик. Можна заборонити зміну підписок під час доставки, як у прикладі. Можна поставити зміни в чергу та застосувати після завершення події. Можна обходити знімок, але тоді життя об’єктів у знімку все одно має бути гарантованим.
Отже, копіювання масиву сирих вказівників саме по собі не вирішує проблему знищення слухача. Воно захищає лише структуру обходу, а не об’єкти за адресами. Розділяйте чинність ітератора, чинність вказівника й предметну політику доставки події.
Інваріант історії undo та redo
Після виконання двох команд A і B стек done містить A, B, а redo порожній. Undo для B відновлює стан після A та переносить B в undone. Redo знову виконує B й повертає його у done. У кожен момент команда належить одному власникові та одному логічному стану.
Якщо після undo виконати C, повернення до старого B вже не відповідає новій історії. Приклад очищує undone після успішного виконання C. Якщо C відхилено до зміни тексту, історія має лишитися попередньою. Саме тому перевірки й можливі алокації розташовують перед необоротним підтвердженням переходу.
Загальний Command-інтерфейс не гарантує автоматично, що execute та undo мають потрібну безпеку винятків. Конкретні команди повинні дотримуватися контракту History. Для складних змін тексту можуть знадобитися знімки, транзакційні обміни або інша структура історії. Короткий приклад Append чесно обмежує доступ до тексту власними командами.