Паттерн State (Стан). Структура. Реалізація на C++
Зміст
- 1. Загальні відомості. Призначення. Діаграма класів
- 2. Учасники паттерну
- 3. Відношення
- 4. Результати
- 5. Випадки застосування паттерну State
- 6. Приклад реалізації паттерну на C++
- 7. Реалізація
- Споріднені теми
1. Загальні відомості. Призначення. Діаграма класів
Паттерн State належить до паттернів поведінки об’єктів і призначений для поміщення станів об’єкту в окремі об’єкти. В залежності від внутрішнього стану об’єкт змінює свою поведінку.
На рисунку 1 зображена структура паттерну State.
Рисунок 1. Структура паттерну State
⇑
2. Учасники паттерну
Згідно з рисунком 1 учасниками паттерну є:
- Client – клієнт, використовує різні стани об’єкту через контекст;
- Context – клас контексту. Це клас, через який клієнт має доступ до конкретного стану об’єкту;
- State – клас стану. Визначає інтерфейс для інкапсуляції поведінки об’єкту. Ця поведінка залежить від конкретного стану Context;
- ConcreteStateA, ConcreteStateB – підкласи, що реалізують відповідну поведінку, яка асоційована з різними станами контексту Context.
⇑
3. Відношення
Для паттерну State можна виділити наступні відношення (див. рисунок 1):
- з допомогою поля state клас Context делегує запити поточному об’єкту. Ці запити залежать від вибраного стану;
- існує можливість передачі об’єкта Context в якості аргументу в об’єкт стану State. У цьому випадку об’єкт стану може отримати доступ до контексту;
- основни інтерфейсом для клієнта є контекст (Context). Можна реалізувати конфігурування клієнтами контексту конкретними об’єктами стану. Після цього можна зручно використовувати ці об’єкти стану;
- зміна стану може відбуватись двома способами: через контекст (Context) або через підкласи конкретних станів (ConcreteState).
⇑
4. Результати
Паттерн State дає наступні результати.
- Поведінка об’єкту, яка залежить від стану, є локалізованою і поділеною на відповідні частини. Кожна частина є окремим станом об’єкту. Нові стани додаються шляхом успадкування нових підкласів.
Перевагою паттерну State є те, що не потрібно використовувати умовні оператори для визначення того чи іншого стану. Замість цього пропонується розподіл між підкласами класу State. Однак, тут негативним явищем може бути збільшення загальної кількості класів.
- Ввід окремих об’єктів для різних станів робить переходи між станами більш явними. Ці переходи є атомарними діями. Для здійснення переходу потрібно змінити значення тільки однієї об’єктної змінної state в класі Context.
- Існує можливість розділення об’єктів стану. В об’єкті стану State той чи інший стан можна кодувати відповідним типом (класом). Тоді різні контексти можуть розділяти один і той самий об’єкт State. У цьому випадку стани є пристосуванцями.
⇑
5. Випадки застосування паттерну State
Паттерн State потрібно використовувати у наступних випадках:
- коли під час виконання потрібно змінювати поведінку об’єкту в залежності від його стану;
- коли код містить багато розгалужень з використанням операторів умови (операторів вибору) і кожне розгалуження є станом об’єкту. У цьому випадку в кожне розгалуження поміщається окремий клас при якому кожен стан трактується як окремий самостійний об’єкт.
⇑
6. Приклад реалізації паттерну на C++
// Паттерн State - стан #include <iostream> using namespace std; // Інтерфейс, що інкапсулює поведінку, // яка відповідає конкретному стану контексту (Context) class State abstract { public: virtual void Handle() = 0; }; // Конкретні стани (поведінки) class ConcreteStateA : public State { public: void Handle() override { cout << "ConcreteStateA::Handle()" << endl; } }; class ConcreteStateB : public State { public: void Handle() override { cout << "ConcreteStateB::Handle()" << endl; } }; // Клас контексту class Context { private: State* state = nullptr; public: // Конструктор Context(State* state = nullptr) : state(state) { } void Request() { state->Handle(); } }; void main() { // Клієнтський код // 1. Створити конкретні стани State* state1 = new ConcreteStateA(); State* state2 = new ConcreteStateB(); // 2. Створити контекст Context* context = nullptr; // 3. Створити меню вибору стану int stateNum; cout << "stateNum = "; cin >> stateNum; // задати стан об'єкту if (stateNum == 1) context = new Context(state1); else context = new Context(state2); // 4. Вивести значення стану context->Request(); // 5. Звільнити пам'ять if (state1) delete state1; if (state2) delete state2; if (context) delete context; }
⇑
7. Реалізація
При реалізації паттерну State виникають наступні питання.
- Немає чіткої інформації про те, хто реалізує перехід між станами. Це може бути контекст, а можуть бути підкласи класу State. У цьому випадку більш гнучким підходом вважається використання підкласів стану, для яких повинен бути реалізований відповідний інтерфейс.
- Для представлення зміни станів можна використовувати таблиці. Ці таблиці здійснюють відображення вхідних даних на переходи між станами. З допомогою цього способу визначається в який стан потрібно перейти при отриманні деяких вхідних даних. Таким чином віртуальні функції замінюються пошуком в таблиці.
- Вибір можливості створення (знищення) об’єктів у процесі розробки. Створювати об’єкти можна при необхідності та на початку. При першому способі об’єкти знищуються одразу після використання. При другому способі об’єкти створюються на початку програми і знищуюються в самому кінці при завершенні програми.
Перший варіант вважається кращим у випадках коли не відомо, в який стан буде попадати система. Тут деякі об’єкти можуть бути взагалі не використані а їх створення забрало ресурси.
Другий спосіб кращий у випадках коли зміни стану відбуваються часто. При цьому економиться час на багатократне створення/знищення об’єкту.
- Використання динамічного успадкування. Тут передбачається зміна поведінки за запитом (клас об’єкту змінюється під час виконання). Але деякі мови програмування цю опцію не підтримують.
⇑
Споріднені теми
- Паттерни поведінки. Огляд
- Паттерн Chain of Responsibility. Загальні відомості. Реалізація на C++
- Паттерн Command. Реалізація структури на C++
- Паттерн Template Method. Реалізація структури на C++
⇑