Паттерн «Состояние» (State)

Описание

Паттерн «Состояние» — это поведенческий паттерн проектирования, который позволяет объекту изменять своё поведение в зависимости от текущего состояния. Этот паттерн инкапсулирует поведения, соответствующие различным состояниям объекта, в отдельные классы. Такой подход помогает избежать громоздких конструкций if-else и switch, делая код более чистым, гибким и удобным для расширения.

Имеет схожую структуру с паттерном «Стратегия». Построен на принципе «композиции», то есть на делегировании работы другим объектам.

«Стратегия» vs «Состояние»

В паттерне «Стратегия» различные стратегии работают независимо друг от друга и не взаимодействуют между собой.

В паттерне «Состояние» состояния могут менять друг друга, поскольку они связаны с общим объектом и управляют его переходами из одного состояния в другое.

Основные элементы паттерна «Состояние»:

  1. Контекст (Context) — основной объект, состояние которого может изменяться в зависимости от вызова различных методов. Контекст управляет переключением между состояниями и предоставляет общий интерфейс для пользователя.
  2. Интерфейс состояния (State) — интерфейс, который определяет общие методы для различных состояний. Эти методы должны быть реализованы во всех конкретных состояниях.
  3. Конкретные состояния (Concrete State) — классы, которые реализуют конкретное поведение для каждого состояния.

Пример: Банкомат

пример с банкоматом, который может находиться в различных состояниях:

  1. Ожидание карты (WaitingCardState): банкомат ожидает, когда пользователь вставит карту.
  2. Вставка карты (CheckCardState): банкомат проверяет пароль от карты.
  3. Выдача наличных (WithdrawCashState): банкомат готов к выдаче наличных после успешного ввода пароля.

Каждое состояние инкапсулирует свою логику, и банкомат переключается между ними в зависимости от пользовательских действий.

Интерфейс состояния

Интерфейс State определяет методы для различных действий, которые пользователь может выполнить в банкомате, таких как вставка карты, ввод пароля, снятие наличных и получение статуса (нужен просто для проверки текущего состояния).

interface State {
  insertCard(): void;
  withdrawCash(sum: number): void;
  checkPassword(passwd: string): void;
  getStatus(): string;
}

Конкретные состояния

Каждое состояние реализует поведение для всех методов, определённых в интерфейсе State. Например:

  • Состояние ожидания карты (WaitingCardState): банкомат ожидает вставки карты. Если пользователь пытается ввести пароль или снять наличные, банкомат уведомляет о необходимости вставить карту.
class WaitingCardState implements State {
  constructor(private cashMachine: CashMachine) {}

  public withdrawCash(sum: number): void {
    console.log('Вставьте карту в банкомат');
  }

  public insertCard(): void {
    console.log('**Банкомат принимает карту**');
    this.cashMachine.setState(this.cashMachine.getCheckCardState());
  }

  public checkPassword(passwd: string): void {
    console.log('Вставьте карту в банкомат');
  }

  public getStatus(): string {
    return 'Ожидание карты';
  }
}
  • Состояние вставки карты (CheckCardState): банкомат ожидает ввода пароля. При успешном вводе банкомат переходит в состояние выдачи наличных, при неудачном — возвращает карту пользователю.
class CheckCardState implements State {
  private attemptCount = 0;
  private readonly maxAttempts = 3;

  constructor(private cashMachine: CashMachine) {}

  public withdrawCash(sum: number): void {
    console.log('Карта активна. Введите пароль');
  }

  public insertCard(): void {
    console.log('Карта активна. Введите пароль');
  }

  public checkPassword(passwd: string): void {
    if (passwd === '1234') {
      console.log('**Введен верный пароль**');
      this.cashMachine.setState(this.cashMachine.getWithdrawCashState());
      this.attemptCount = 0;
    } else {
      this.attemptCount++;
      console.log(`Неверный пароль. Попытка ${this.attemptCount}/${this.maxAttempts}.`);
      if (this.attemptCount >= this.maxAttempts) {
        console.log('**Банкомат вернул карту**');
        this.cashMachine.setState(this.cashMachine.getWaitingCardState());
        this.attemptCount = 0;
      }
    }
  }

  public getStatus(): string {
    return 'Карта вставлена в банкомат';
  }
}
  • Состояние выдачи наличных (WithdrawCashState): банкомат позволяет пользователю снять наличные, после чего возвращает карту и переходит в состояние ожидания карты.
class WithdrawCashState implements State {
  constructor(private cashMachine: CashMachine) {}

  public withdrawCash(sum: number): void {
    if (this.cashMachine.cash < sum) {
      console.log('В банкомате недостаточно средств!');
    } else {
      this.cashMachine.cash -= sum;
      console.log(`Наличные выданы. В банкомате осталось: ${this.cashMachine.cash} рублей`);
      console.log('**Банкомат вернул карту**');
      this.cashMachine.setState(this.cashMachine.getWaitingCardState());
    }
  }

  public insertCard(): void {
    console.log('Ваша карта уже активна. Введите сумму для снятия');
  }

  public checkPassword(passwd: string): void {
    console.log('**Происходит выдача наличных**');
  }

  public getStatus(): string {
    return 'Выдача наличных';
  }
}

Контекст (класс CashMachine)

Контекст управляет текущим состоянием и предоставляет методы для вызова действий, которые делегируются текущему состоянию. В зависимости от состояния банкомат выполняет соответствующие действия.

class CashMachine {
  private state: State;
  private waitingCardState: State;
  private CheckCardState: State;
  private withdrawCashState: State;

  constructor(public cash: number) {
    this.waitingCardState = new WaitingCardState(this);
    this.CheckCardState = new CheckCardState(this);
    this.withdrawCashState = new WithdrawCashState(this);
    this.state = this.waitingCardState;
  }

  public setState(state: State): void {
    this.state = state;
  }

  public withdrawCash(sum: number): void {
    this.state.withdrawCash(sum);
  }

  public insertCard(): void {
    this.state.insertCard();
  }

  public checkPassword(passwd: string): void {
    this.state.checkPassword(passwd);
  }

  public getWithdrawCashState(): State {
    return this.withdrawCashState;
  }

  public getWaitingCardState(): State {
    return this.waitingCardState;
  }

  public getCheckCardState(): State {
    return this.CheckCardState;
  }

  public showStatus(): void {
    console.log(`Текущий статус: ${this.state.getStatus()}`);
  }

  public showAmountOfCash(): void {
    console.log(`В банкомате находятся ${this.cash} рублей`);
  }
}

Преимущества

  1. Упрощение кода: логика, связанная с различными состояниями, разделена на отдельные классы, что делает код более понятным и лёгким для поддержки.
  2. Расширяемость: новые состояния можно добавлять без изменения существующего кода контекста, что соответствует принципу открытости/закрытости (OCP).
  3. Инкапсуляция поведения: каждый класс состояния инкапсулирует логику, что позволяет объекту (банкомату) менять своё поведение в зависимости от состояния, не меняя структуры самого объекта.

Заключение

Паттерн «Состояние» — инструмент для управления поведением объектов с различными состояниями. Он позволяет отделить логику каждого состояния в отдельные классы, упростить код, сделать его гибким и поддерживаемым, что особенно полезно при большом числе взаимосвязанных состояний и сложных переходах между ними.

Ссылки

Последние обновления

© 2023 — 2026 nbeam.ru