Застряв на уроке “Паттерн 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');
});
});
Теперь приступим к реализации, напомню общую схему паттерна.
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;
}
}
Это все, надеюсь у вас получилось собрать файлы и тесты прошли.