Patterns. Паттерн State (Стан) реалізація схеми)

Паттерн State (Стан). Структура. Реалізація на C++


Зміст


1. Загальні відомості. Призначення. Діаграма класів

Паттерн State належить до паттернів поведінки об’єктів і призначений для поміщення станів об’єкту в окремі об’єкти. В залежності від внутрішнього стану об’єкт змінює свою поведінку.

На рисунку 1 зображена структура паттерну State.

Паттерн State. Структура

Рисунок 1. Структура паттерну State

 

2. Учасники паттерну

Згідно з рисунком 1 учасниками паттерну є:

  • Client – клієнт, використовує різні стани об’єкту через контекст;
  • Context – клас контексту. Це клас, через який клієнт має доступ до конкретного стану об’єкту;
  • State – клас стану. Визначає інтерфейс для інкапсуляції поведінки об’єкту. Ця поведінка залежить від конкретного стану Context;
  • ConcreteStateA, ConcreteStateB – підкласи, що реалізують відповідну поведінку, яка асоційована з різними станами контексту Context.

 

3. Відношення

Для паттерну State можна виділити наступні відношення (див. рисунок 1):

  • з допомогою поля state клас Context делегує запити поточному об’єкту. Ці запити залежать від вибраного стану;
  • існує можливість передачі об’єкта Context в якості аргументу в об’єкт стану State. У цьому випадку об’єкт стану може отримати доступ до контексту;
  • основни інтерфейсом для клієнта є контекст (Context). Можна реалізувати конфігурування клієнтами контексту конкретними об’єктами стану. Після цього можна зручно використовувати ці об’єкти стану;
  • зміна стану може відбуватись двома способами: через контекст (Context) або через підкласи конкретних станів (ConcreteState).

 

4. Результати

Паттерн State дає наступні результати.

  1. Поведінка об’єкту, яка залежить від стану, є локалізованою і поділеною на відповідні частини. Кожна частина є окремим станом об’єкту. Нові стани додаються шляхом успадкування нових підкласів.

Перевагою паттерну State є  те, що не потрібно використовувати умовні оператори для визначення того чи іншого стану. Замість цього пропонується розподіл між підкласами класу State. Однак, тут негативним явищем може бути збільшення загальної кількості класів.

  1. Ввід окремих об’єктів для різних станів робить переходи між станами більш явними. Ці переходи є атомарними діями. Для здійснення переходу потрібно змінити значення тільки однієї об’єктної змінної state в класі Context.
  2. Існує можливість розділення об’єктів стану. В об’єкті стану 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 виникають наступні питання.

  1. Немає чіткої інформації про те, хто реалізує перехід між станами. Це може бути контекст, а можуть бути підкласи класу State. У цьому випадку більш гнучким підходом вважається використання підкласів стану, для яких повинен бути реалізований відповідний інтерфейс.
  2. Для представлення зміни станів можна використовувати таблиці. Ці таблиці здійснюють відображення вхідних даних на переходи між станами. З допомогою цього способу визначається в який стан потрібно перейти при отриманні деяких вхідних даних. Таким чином віртуальні функції замінюються пошуком в таблиці.
  3. Вибір можливості створення (знищення) об’єктів у процесі розробки. Створювати об’єкти можна при необхідності та на початку. При першому способі об’єкти знищуються одразу після використання. При другому способі об’єкти створюються на початку програми і знищуюються в самому кінці при завершенні програми.

Перший варіант вважається кращим у випадках коли не відомо, в який стан буде попадати система. Тут деякі об’єкти можуть бути взагалі не використані а їх створення забрало ресурси.

Другий спосіб кращий у випадках коли зміни стану відбуваються часто. При цьому економиться час на багатократне створення/знищення об’єкту.

  1. Використання динамічного успадкування. Тут передбачається зміна поведінки за запитом (клас об’єкту змінюється під час виконання). Але деякі мови програмування цю опцію не підтримують.

 


Споріднені теми