Паттерн State

Застряв на уроке “Паттерн State” курса “JS: Автоматное программирование”. Я решил разобраться с этим получше и из этого получался этот пост. Может быть кому станет легче или понятнее проходить этот урок.

В уроке “Паттерн State” нужно сделать будильник, в курсе довольно резки скачок сложности в этом месте, потому что до этого вы как бы ничего не делали, а тут бац и пишите автомат. Рад за тех, кто сразу въезжат, что надо сделать, но в данном случае это не я.

Напишем управление светофора с помощью паттерна state, кода не много, надеюсь уловите общую идею.

Начнем с тестов.

Первый тест будет проверять дефолтные значения, дефолтным поставим красный свет.

it('should have default values', () => {
  const light = new TrafficLight();
  expect(light.getCurrentMode()).toBe('red');
});

В следующем тесте проверим логику работы светофора. Логика такая: 3 тика красный свет, 1 тик желтого, 3 тика зеленого, возвращаемся к красному и так светофор работает по кругу.

it('should change state', () => {
  const light = new TrafficLight();
  expect(light.getCurrentMode()).toBe('red');
  light.tick();
  expect(light.getCurrentMode()).toBe('red');
  light.tick();
  expect(light.getCurrentMode()).toBe('red');
  light.tick();
  expect(light.getCurrentMode()).toBe('yellow');
  light.tick();
  expect(light.getCurrentMode()).toBe('green');
  light.tick();
  expect(light.getCurrentMode()).toBe('green');
  light.tick();
  expect(light.getCurrentMode()).toBe('green');
  light.tick();
  expect(light.getCurrentMode()).toBe('red');
});

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

it('should be red', () => {
  const light = new TrafficLight();
  for (let i = 1; i < 22; i += 1) {
    light.tick();
  }
  expect(light.getCurrentMode()).toBe('red');
});
it('should be yellow', () => {
  const light = new TrafficLight();
  for (let i = 1; i < 39; i += 1) {
    light.tick();
  }
  expect(light.getCurrentMode()).toBe('yellow');
});
it('should be green', () => {
  const light = new TrafficLight();
  for (let i = 1; i < 62; i += 1) {
    light.tick();
  }
  expect(light.getCurrentMode()).toBe('green');
});

Весь файл с тестами.

import TrafficLight from './TrafficLight';

describe('TrafficLight', () => {
  it('should have default values', () => {
    const light = new TrafficLight();
    expect(light.getCurrentMode()).toBe('red');
  });
  it('should change state', () => {
    const light = new TrafficLight();
    expect(light.getCurrentMode()).toBe('red');
    light.tick();
    expect(light.getCurrentMode()).toBe('red');
    light.tick();
    expect(light.getCurrentMode()).toBe('red');
    light.tick();
    expect(light.getCurrentMode()).toBe('yellow');
    light.tick();
    expect(light.getCurrentMode()).toBe('green');
    light.tick();
    expect(light.getCurrentMode()).toBe('green');
    light.tick();
    expect(light.getCurrentMode()).toBe('green');
    light.tick();
    expect(light.getCurrentMode()).toBe('red');
  });
  it('should be red', () => {
    const light = new TrafficLight();
    for (let i = 1; i < 22; i += 1) {
      light.tick();
    }
    expect(light.getCurrentMode()).toBe('red');
  });
  it('should be yellow', () => {
    const light = new TrafficLight();
    for (let i = 1; i < 39; i += 1) {
      light.tick();
    }
    expect(light.getCurrentMode()).toBe('yellow');
  });
  it('should be green', () => {
    const light = new TrafficLight();
    for (let i = 1; i < 62; i += 1) {
      light.tick();
    }
    expect(light.getCurrentMode()).toBe('green');
  });
});

Теперь приступим к реализации, напомню общую схему паттерна.

State Design Pattern UML Class Diagram
State Design Pattern UML Class Diagram

Context у нас будет сам светофор, назовем его TrafficLight.

State это стейт, так и назовем State.

ConcreteState это отдельный стейт, у нас их три красный, желтый и зеленый, назовем их RedState, YellowState, GreenState.

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

state.handle() это вызов обработчика текущего стейта т.к. у нас меняется стейт в Context одинаковое имя обработчика, может по разному изменять стейт.

Вы наверно спросите, что такое Request()? На это наука еще не нашла ответа.

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

Такое файловое дерево.

test.js
TrafficLight.js
State.js
RedState.js
YellowState.js
GreenState.js

Начнем с контекста т.е. с самого светофора. У нас класс TrafficLight в котором по дефолту ставится красный свет, в классе три метода, setState установка стейта, getCurrentMode получение текущего режима, tick тик светофора (типо время работы или мигание).

import RedState from './RedState';

export default class TrafficLight {
  constructor() {
    this.setState(RedState);
  }

  setState(Klass) {
    this.state = new Klass(this);
  }

  getCurrentMode() {
    return this.state.getModeName();
  }

  tick() {
    this.state.countTick = this.state.countTick - 1;
    if (this.state.countTick === 0) {
      this.state.nextState();
    }
  }
}

Дальше стейт. В нем light это текущий стейт и два метода, nextState переключает на следующий стейт и getModeName возращает текущий режим.

export default class State {
  constructor(light) {
    this.light = light;
  }

  nextState(StateKlass) {
    this.light.setState(StateKlass || this.NextStateClass);
  }

  getModeName() {
    return this.mode;
  }
}

Дальше конкретные стейты красный, желтый, зеленый. В них только установка режима, установка количества тиков и следующий стейт.

import State from './State';
import YellowState from './YellowState';

export default class RedState extends State {
  constructor(light) {
    super(light);
    this.mode = 'red';
    this.countTick = 3;
    this.NextStateClass = YellowState;
  }
}
import State from './State';
import GreenState from './GreenState';

export default class YellowState extends State {
  constructor(light) {
    super(light);
    this.mode = 'yellow';
    this.countTick = 1;
    this.NextStateClass = GreenState;
  }
}
import State from './State';
import RedState from './RedState';

export default class GreenState extends State {
  constructor(light) {
    super(light);
    this.mode = 'green';
    this.countTick = 3;
    this.NextStateClass = RedState;
  }
}

Это все, надеюсь у вас получилось собрать файлы и тесты прошли.

Subscribe to my mailing list

* indicates required
Share
Send

Related Posts