Мета і підготовка
Побудувати машину станів, реалізувати переходи та перевірити дозволені й відхилені події. Потрібно знати enum, class, private set і guard. Створіть dotnet new console -n StateLab, відкрийте Program.cs. Використайте той самий підтримуваний SDK, що в попередній роботі; перевірте dotnet run до додавання власного коду.
Теоретичний мінімум
Стан визначає допустимі наступні події. Guard є умовою переходу; ефект змінює стан. Для навчального квитка погоджено Draft → Submitted → Approved. Відправити можна лише Draft, погодити — лише Submitted. Повторна дія відхиляється без зміни стану. Таблиця переходів є незалежною специфікацією для перевірки реалізації.
Не використовуйте кілька bool для взаємовиключних станів, якщо це дозволяє некоректні комбінації. Enum робить перелік видимим, але сама властивість усе ще потребує контрольованого запису. Реальна модель також може мати роль погоджувача й причину відмови; у демонстрації вони не включені, щоб зосередитися на механізмі переходу.
Повний код демонстрації
using System;
var ticket = new Ticket();
Check(ticket.State == TicketState.Draft, "Початковий стан");
Check(!ticket.Approve(), "Draft не погоджується");
Check(ticket.Submit(), "Відправлення");
Check(!ticket.Submit(), "Повторне відправлення");
Check(ticket.Approve(), "Погодження");
Check(ticket.State == TicketState.Approved, "Кінцевий стан");
Console.WriteLine(ticket.State);
static void Check(bool condition, string label)
{
if (!condition) throw new Exception(label);
}
public enum TicketState { Draft, Submitted, Approved }
public sealed class Ticket
{
public TicketState State { get; private set; } = TicketState.Draft;
public bool Submit()
{
if (State != TicketState.Draft) return false;
State = TicketState.Submitted;
return true;
}
public bool Approve()
{
if (State != TicketState.Submitted) return false;
State = TicketState.Approved;
return true;
}
}
Очікується Approved. Check завершує програму помилкою при невиконаному очікуванні. Кожен метод перевіряє guard до запису. Зовнішній код бачить стан, але не встановлює його напряму. Окремо додайте перевірку збереження Draft після відхиленого Approve: результат false і незмінність стану є двома частинами контракту.
Покрокове виконання
Складіть таблицю всіх пар «стан × подія» для Submit і Approve. Для кожної вкажіть дозвіл і наступний стан. Намалюйте state diagram зі стрілками та guards. Запустіть демонстрацію й зіставте переходи з таблицею. Потім навмисно приберіть guard з Approve та перевірте, який тест виявив дефект. Поверніть правильний контракт.
Опишіть use case погодження з основним і альтернативним потоками. Побудуйте sequence diagram клієнт → сервіс → Ticket. Поясніть, які перевірки виконує предметний об’єкт, а які ще потрібні сервісу для авторизації. Стани моделі не мають автоматично означати право користувача на дію.
Самостійне завдання
Створіть машину станів видачі обладнання: Available, Reserved, Issued, Returned або інший погоджений перелік. Визначте різницю між історичною подією повернення та поточним станом доступності. Додайте перевірку власника резервації для хоча б однієї операції. Кожен перехід повинен мати подію, guard і ефект.
Напишіть тести всіх дозволених переходів та щонайменше чотирьох заборонених. Для кожної відмови перевірте незмінність стану. Не копіюйте Ticket як готову модель обладнання: потрібні власні предметні правила. Додатковий варіант — стан Maintenance і правило повернення несправного пристрою.
Помилки та налагодження
Відсутній перехід на схемі може означати прогалину вимог. Довільне присвоєння enum обходить guard. Повернення true без зміни стану суперечить контракту. Якщо тест провалився, запишіть початковий стан, подію, аргументи, результат і кінцевий стан. Відтворіть найкоротшу послідовність до збою.
Результат і контрольні питання
Подайте таблицю, state і sequence моделі, код і результати перевірок. Поясніть, чому повторна операція відхиляється, де перевіряється користувач і що гарантує private set. Чи можна відновитися з кожного стану? Який сценарій змінить модель, якщо з’явиться скасування після погодження?
Таблиця переходів і перевірка незмінності
Створіть таблицю зі стовпцями поточний стан, дія, guard, наступний стан та результат методу. Для кожного дозволеного переходу додайте недозволений виклик із того самого стану. Якщо Check повертає false, повторно прочитайте стан і переконайтеся, що він не змінився. Сам bool не доводить збереження інваріанта.
У власній моделі обладнання перелік станів має відображати процес: доступне, зарезервоване, видане або на обслуговуванні відповідно до обраних вимог. Визначте, чи повернення завжди робить ресурс доступним, чи він може потребувати перевірки. Погоджене правило повинне бути однаковим у коді та діаграмі.
Для налагодження збережіть коротку трасу дій і стан після кожного виклику. Послідовність reserve, issue, return допомагає перевірити звичайний цикл; повторний issue і return з початкового стану перевіряють відмови. Якщо проблема виникає лише після кількох дій, відтворіть мінімальну послідовність, що її спричиняє.
У висновку назвіть, які переходи перевірено, і покажіть відповідність між enum, методом та guard. Не додавайте універсальний публічний SetState для обходу процесу: він дозволив би пропустити потрібні передумови. Якщо потрібна адміністративна дія, опишіть її окремий контракт і спосіб контролю. Так модель зберігає зрозумілу відповідальність за всі зміни.
Окремий контрольний сценарій
Перевірте повторний виклик уже виконаної дії. Наприклад, повторне підтвердження може бути стабільним успіхом або визначеною відмовою залежно від контракту. Вкажіть, чи воно створює нову подію, чи лише повертає поточний стан. Така перевірка важлива для повторних мережевих запитів. У власній моделі оберіть одну дію й поясніть її ідемпотентність або причину, чому повтор потребує окремого обмеження.
Схема процесу
Визначаємо всі пари стану й події.
Самоперевірка
Як обґрунтувати відповідь своїми словами?
Підсумок
Машина станів робить правила переходів видимими. Таблиця та сценарії допомагають перевірити узгодженість діаграм і коду. Відмова, права доступу й незмінність стану потребують явного контракту.