Модель состояния JavaScript и модель конечного автомата

внешний интерфейс JavaScript

Это статья, которую я не могу прочитать сам...

Статья условно разделена на две части:

  1. Введение в режим состояний

  2. Использование и анализ исходного кода библиотеки функций javascript-state-machine модели конечного автомата

Сценарий и предыстория проблемы:

Когда мы обычно разрабатываем, мы по существу переключаем различные состояния приложения и соответствующим образом с ними работаем. Наиболее прямое решение состоит в том, чтобы принять во внимание все эти возможные ситуации, а затем использовать операторы if... else для вынесения суждений о состоянии для обработки различных ситуаций. Однако оценка сложных состояний делает логику кода особенно хаотичной. При добавлении нового состояния или изменении состояния оператор if else или switch case будет соответствующим образом увеличен или изменен, а удобочитаемость и масштабируемость программы станут слабыми. Техническое обслуживание также может быть обременительным.

Возьмем пример, вы когда-нибудь играли в Contra? :

Давайте сначала рассмотрим простую реализацию простейшего действия:

  1. class Contra {

  2.  constructor () {

  3.    //存储当前待执行的动作

  4.    this.lastAct = {};

  5.  }

  6.  //执行动作

  7.  contraGo (act){

  8.    if(act === 'up'){

  9.      //向上跳

  10.    }else if(act === 'forward'){

  11.      //向前冲啊

  12.    }else if(act === 'backward'){

  13.      //往老家跑

  14.    }else if(act === 'down'){

  15.      //趴下

  16.    }else if(act === 'shoot'){

  17.      //开枪

  18.    }

  19.    this.lastAct = act;

  20.  }

  21. };

  22. var littlered = new Contra();

  23. littlered.contraGo('shoot');

скопировать код

Что делать, если есть два комбинированных действия? Измени это:

  1. function contraGo (act){

  2.  constructor () {

  3.    //存储当前待执行的动作

  4.    this.lastAct1 = "";

  5.    this.lastAct2 = "";

  6.  }

  7.  contraGo (act1, act2){

  8.    const actArr = [act1, act2];

  9.    if(actArr.indexOf('shoot') !== -1 &&  actArr.indexOf('up') !== -1){

  10.      //跳着开枪吧

  11.    }else if(actArr.indexOf('shoot') !== -1 &&  actArr.indexOf('forward') !== -1){

  12.      //向前跑着开枪吧

  13.    }else if(actArr.indexOf('shoot') !== -1 &&  actArr.indexOf('down') !== -1){

  14.      //趴着开枪吧

  15.    }else if(actArr.indexOf('shoot') !== -1 &&  actArr.indexOf('backward') !== -1){

  16.      //回头跑着开枪吧

  17.    }else if(actArr.indexOf('up') !== -1 &&  actArr.indexOf('forward') !== -1){

  18.      //向前跳吧

  19.    }else if(actArr.indexOf('up') !== -1 &&  actArr.indexOf('down') !== -1){

  20.      //上上下下吧

  21.    }

  22.  ...//等等组合

  23.    this.lastAct1 = act1;

  24.    this.lastAct2 = act2;

  25.  }

  26. }

  27. var littlered = new Contra();

  28. littlered.contraGo('shoot');

скопировать код

Недостатки очевидны.Много if else суждений.При добавлении оглядки на Xiaohong и Xiaolan приходится модифицировать метод contraGo и добавлять кучу перестановок и комбинаций,что делает contraGo очень неудобным.Стабильный метод, и чем больше состояний, тем больше, возвышеннее метод contraGo нарушает принцип открытого-закрытого!

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


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

В чем болевые точки — в процессе разработки, когда исходную логику нужно модифицировать из-за изменений, апгрейдов и сопровождения и т.п., очень вероятно, что в старый код будут внесены ошибки, или нам, возможно, придется проводить рефакторинг всю функцию, и требуется новое тестирование исходной функции.

Как это решить - мы должны попытаться внедрить изменения, расширив поведение сущностей, а не модифицируя существующий код для реализации изменений.

Если быть точным - классы, модули и функции должны быть открыты для расширения и закрыты для модификации. Модули должны расширяться без изменения исходного кода.

Ядро — создавайте фреймворки с абстракциями и расширяйте детали с помощью реализаций.

Подводя итог - разработчики должны абстрагировать часто меняющиеся части программы, а затем расширять класс реализации, производный от абстракции.При изменении кода необходимо только заново разработать класс реализации в соответствии с требованиями. От нас требуется быть дальновидными и предсказуемыми к изменениям требований, и в то же время отказываться от преднамеренного абстрагирования каждой части приложения.

Включая открытый и закрытый принципы, шесть принципов шаблонов проектирования здесь подробно не описываются, но кратко перечислены ниже:

  1. Единый принцип (SRP):Класс реализации должен нести единую ответственность. Класс делает только одну вещь или один тип вещей. Не смешивайте функции, которые нельзя разделить на один тип. Обещай мне

  2. Принцип замены Лисков (LSP):Не разрушайте систему наследования.Подклассы могут полностью заменить родительский класс, который они наследуют.При этом можно понять, что место вызова метода родительского класса заменяется подклассом, и вызов может выполняться нормально.Сын страны, заложенной отцом, получится без нажима, верно?

  3. Принцип инверсии зависимостей (DIP):Позвольте мне объяснить вам мое понимание: если определенный набор функций или бизнес-логики может иметь другой параллельный режим или серьезные корректировки позже, лучше абстрагировать эту часть логики, создать абстрактный класс, содержащий связанные методы, и реализовать Класс наследует этот абстрактный класс, чтобы переписать методы в абстрактном классе и завершить конкретную реализацию.Класс, который вызывает эти функциональные методы, не должен заботиться о конкретной реализации этих методов, которые он вызывает, а только вызывает формальные методы, определенные в этих абстрактных достаточно методов, и нет прямой зависимости от класса, который реально реализует эти методы, что удобно для замены и изменения логики реализации в будущем;

  4. Принцип разделения интерфейсов (ISP):При разработке абстрактного класса он должен быть упрощенным и единым.На просторечии А должен полагаться на некоторые методы, предоставляемые Б. Я использую только 3 метода Б, и Б постарается не давать методы, которые А не может использовать;

  5. Закон Деметры (LoD)Уменьшите связанность и сведите к минимуму прямое взаимодействие между объектами.Если одному из классов необходимо вызвать метод другого класса, вызов можно инициировать через класс отношений, чтобы при изменении модуля влияние можно было свести к минимуму.

  6. Принцип открытия-закрытия (OCP)Скажи открыть для расширения и закрыть для модификации Ты можешь унаследовать и расширить все мои способности В твоих руках ты можешь изменить что хочешь, но меня не трогай, ладно? Ok

23 шаблона проектирования суммируются на основе 6 основных принципов, указанных выше, в сочетании с конкретными методами разработки. Все изменения неотделимы от оригинала. С учетом этих основных норм осознания то, что вы пишете, может быть определенным шаблоном проектирования ٩(๑>◡


решение

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

Давайте сначала посмотрим на модифицированный код, используя режим состояния:

  1. class Contra {

  2.  constructor () {

  3.    //存储当前待执行的动作 们

  4.    this._currentstate = {};

  5.  }

  6.  //添加动作

  7.  changeState (){

  8.    //清空当前的动作集合

  9.    this._currentstate = {};

  10.    //遍历添加动作

  11.    Object.keys(arguments).forEach(

  12.      (i) => this._currentstate[arguments[i]] = true

  13.    )

  14.    return this;

  15.  }

  16.  //执行动作

  17.  contraGo (){

  18.    //当前动作集合中的动作依次执行

  19.    Object.keys(this._currentstate).forEach(

  20.      (k) => Actions[k] && Actions[k].apply(this)

  21.    )

  22.    return this;

  23.  }

  24. };

  25. const Actions = {

  26.  up : function(){

  27.    //向上跳

  28.    console.log('up');

  29.  },

  30.  down : function(){

  31.    //趴下

  32.    console.log('down');

  33.  },

  34.  forward : function(){

  35.    //向前跑

  36.    console.log('forward');

  37.  },

  38.  backward : function(){

  39.    //往老家跑

  40.    console.log('backward');

  41.  },

  42.  shoot : function(){

  43.    //开枪吧

  44.    console.log('shoot');

  45.  },

  46. };

  47. var littlered = new Contra();

  48. littlered.changeState('shoot','up').contraGo();

скопировать код

Консоль выдаст: стрелять

Решения:

Режим состояния преобразует результат условного суждения во внутреннее состояние объекта состояния (вверх, вниз, назад, вперед в коде).Внутреннее состояние обычно используется как частная переменная (this._currentState) внутри объекта состояния. , а затем предоставляет состояние, которое можно вызвать.Объект метода интерфейса (changeState, contraGo) внутреннего состояния объекта, так что изменение состояния и модификация и добавление вызова метода состояния также очень easy, что облегчает управление внутренним состоянием в объекте состояния.

В то же время шаблон состояния помещает каждую условную ветвь в отдельный класс, который в коде является Actions. Это позволяет использовать состояние объекта (действие - вверх, вниз, назад, вперед) как объекта (Действия.вверх, Действия.вниз и т.д.) по самому объекту, который может быть независимым от других объектов. Изменение (одно поведение, одно действие, не мешая друг другу).

Видно, что режим состояния — это режим проектирования, подходящий для различных сценариев состояния.После перезаписи код становится более четким, а ремонтопригодность и масштабируемость кода улучшаются.

Сценарии использования режима состояния:

  1. Объект, который имеет различное поведение, вызванное одним или несколькими динамически изменяющимися свойствами.При взаимодействии с внешними событиями его внутреннее состояние будет меняться, так что поведение системы также изменится.Тогда этот объект является объектом с состоянием

  2. Код содержит большое количество условных операторов, связанных с состоянием объекта, таких как операторы if else или switch case, и выполнение этих условий зависит от состояния объекта.

Если сцена удовлетворяет двум вышеуказанным условиям, то мы можем представить, может ли паттерн состояний помочь

Преимущества и недостатки государственного шаблона:

преимущество:

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

  2. Состояние и состояние, поведение и поведение независимы друг от друга и не мешают друг другу

  3. Избегайте постоянного расширения самого объекта и слишком большого количества условных суждений.

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

недостаток:

  1. Необходимо разделять разные состояния вещей и соответствующие им поведения. Иногда очень тонкое разделение неизбежно. Иногда это требует бизнес-логики. Одно действие разбивается на два соответствующих состояния, и действие не понимается.

  2. Вещи определенно увеличат количество классов и действий класса, действий класса, а иногда и дольше, основанных на едином принципе, разделенных на несколько категорий в соответствии с функцией, но сделают код путаным, уменьшит читабельность

Пример сценария режима состояния

Шаблоны состояний в разработке компонентов — навигация

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

Используя ту же идею, что и выше, давайте возьмем небольшой пример компонента React, такого как навигация по баннеру, самые основные два состояния, показать и скрыть, следующие:

  1. const States = {

  2.  "show": function () {

  3.    console.log("banner展现状态,点击关闭");

  4.    this.setState({

  5.      currentState: "hide"

  6.    })

  7.  },

  8.  "hide": function () {

  9.    console.log("banner关闭状态,点击展现");

  10.    this.setState({

  11.      currentState: "show"

  12.    })

  13.  }

  14. };

скопировать код

Аналогично, объект States используется для определения состояния баннера.Есть два состояния show и hide, которые имеют соответствующие методы обработки соответственно.После вызова текущий баннер перезаписывается в другое состояние. Далее давайте посмотрим на класс навигации Banner:

  1. class Banner extends Component{

  2.  constructor(props) {

  3.    super(props);

  4.    this.state = {

  5.      currentState: "hide"

  6.    }

  7.    this.toggle = this.toggle.bind(this);

  8.  }

  9.  toggle () {

  10.    const s = this.state.currentState;

  11.    States[s] && States[s].apply(this);

  12.  }

  13.  render() {

  14.    const { currentState } = this.state;

  15.    return (

  16.      <div className="banner" onClick={this.toggle}>

  17.      </div>

  18.    );

  19.  }

  20. };

  21. export default Banner;

скопировать код

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

Если есть третье состояние, нам нужны только состояния, чтобы добавить соответствующее состояние и обработчик.

Классический пример — светофоры

Все понимают основные функции светофора.Вот, вставьте код напрямую, чтобы добиться той же идеи, а затем измените способ написания:

  1. var trafficLight = (function () {

  2.  var currentLight = null;

  3.  return {

  4.    change: function (light) {

  5.      currentLight = light;

  6.      currentLight.go();

  7.    }

  8.  }

  9. })();

  10. function RedLight() { }

  11. RedLight.prototype.go = function () {

  12.  console.log("this is red light");

  13. }

  14. function GreenLight() { }

  15. GreenLight.prototype.go = function () {

  16.  console.log("this is green light");

  17. }

  18. function YellowLight() { }

  19. YellowLight.prototype.go = function () {

  20.  console.log("this is yellow light");

  21. }

  22. trafficLight.change(new RedLight());

  23. trafficLight.change(new YellowLight());

скопировать код

trafficLight — это экземпляр светофора. В него передается конструктор. Объект предоставляет метод change для изменения внутреннего состояния, то есть цвета света. Change получает тот же объект состояния, а метод объекта вызывается для запуска ответного действия.Здесь все действия называются go, световые объекты разных цветов имеют разные реализации go.

При передаче объекта light методу change состояние светофора изменяется и запускается соответствующий обработчик, что является типичным применением шаблона состояния.

Из приведенных выше трех примеров мы можем почувствовать, что код, использующий режим состояния, имеет ту же структуру кода и ту же функцию.

Эти сценарии на самом деле могут быть представлены общей моделью, «конечным автоматом».

Конечный автомат

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

  1. Вещи имеют несколько состояний, и в любое время они будут только в одном состоянии, а не в нескольких состояниях;

  2. Действия могут изменить состояние вещей. О действии можно судить по условиям, чтобы перевести вещи в разные состояния, но оно не может указывать на несколько состояний одновременно. Один раз, только одно состояние, только одно. (Некоторые здесь могут усомниться в приведенном выше примере Contra. Почему Сяохун и Сяолань могут прыгать и стрелять одновременно? Разве это не два состояния одновременно? " и "стрельба" вместе определяют состояние "прыжки и стрельба". Общее состояние персонажей - это на самом деле расположение и комбинация поведений, которые могут происходить одновременно. Комбинация не организована, потому что прыжки и стрельба и стрельба прыжки Должно быть то же, так еще состояние, а)

  3. Общее количество состояний ограничено;

Поскольку все, что соответствует приведенным выше сценариям, может быть описано моделью конечного автомата, можем ли мы абстрагировать эту модель и реализовать различные процессы состояния, передавая параметры, без необходимости каждый раз писать этот процесс заново?

На гитхабе есть одинФункциональная библиотека для конечных автоматов, зайди и узнай

Библиотека конечных автоматов Javascript Конечный автомат

инструкции

Введение автора на github очень подробное, https://github.com/jakesgordon/javascript-state-machine, здесь я просто расскажу о лени.

Создайте конечный автомат классического примера светофора, посмотрите, хорошо ли я отреагирую на текст хахахахаха:

  1. const StateMachine = require('javascript-state-machine');

  2. var fsm = new StateMachine({

  3.    init: 'green',

  4.    transitions: [

  5.      { name: 'warn',  from: 'green',  to: 'yellow' },

  6.      { name: 'panic', from: 'yellow', to: 'red'    },

  7.      { name: 'calm',  from: 'red',    to: 'yellow' },

  8.      { name: 'clear', from: 'yellow', to: 'green'  }

  9.    ],

  10.    methods: {

  11.      onWarn: function() {},

  12.      onBeforeWarn: function() {},

  13.      onLeaveWarn: function() {},

  14.      onEnterYellow: function() {},

  15.      onLeaveYellow: function() {},

  16.      //...

  17.    }  

  18.  })

скопировать код

Атрибут init начального состояния светофора имеет зеленый цвет, а trainstions — это массив для описания правил изменения состояния, каждый элемент — это объект, который можно понимать как описание правила:

  • форма: из какого состояния исходит текущее поведение

  • to: в какое состояние перейдет текущее поведение после выполнения

  • name: название текущего поведения

После того, как экземпляр сгенерирован, получаем состояние текущего объекта конечного автомата fsm:

  1. fsm.state

скопировать код

Несколько методов перехода для изменения состояния конечного автомата

  1. fsm.warn()

  2. fsm.panic()

  3. fsm.calm()

  4. fsm.clear()

скопировать код

В то же время государственная машина предоставляет ряд инструментов и методов

  1. fsm.is(s) - return true 如果当前状态机状态为 s

  2. fsm.can(t) - return true 如果过渡方法t可以从当前状态触发

  3. fsm.cannot(t) - return true 如果当前状态下不能发生过渡方法t

  4. fsm.transitions() - 返回从当前状态可以过渡到的状态的列表

  5. fsm.allTransitions() - 返回所有过渡方法的列表

  6. fsm.allStates() - 返回状态机有的所有状态的列表

скопировать код

Перечислены методы жизненного цикла, а жизненный цикл изменений будет выполняться в следующем порядке:

  1. //注意,STATE是当前状态机所处的状态,TRANSITION是即将发生的动作

  2. onBeforeTransition 任何动作触发前触发

  3. onBefore<TRANSITION> 在特定动作TRANSITION前触发

  4. onLeaveState 离开任何一个状态的时候触发

  5. onLeave<STATE> 在离开特定状态STATE时触发

  6. onTransition 在任何动作发生期间触发

  7. onEnterState 当进入任何状态时触发

  8. onEnter<STATE> 进入一个特定的状态STATE时触发

  9. on<STATE> onEnter<STATE>的简写

  10. onAfterTransition 任何动作触发后触发

  11. onAfter<TRANSITION> 在特定动作TRANSITION后触发

  12. on<TRANSITION> onAfter<TRANSITION>的简写

скопировать код

В сочетании с методом цикла объявления объясните методы в атрибуте методов в приведенном выше примере:

  1. methods: {

  2.    onWarn: function() {},//在warn动作发生时触发

  3.    onBeforeWarn: function() {},//在warn动作要发生时触发

  4.    onLeaveWarn: function() {},//在warn动作发生后触发

  5.    onEnterYellow: function() {},//在变成黄灯状态时触发

  6.    onLeaveYellow: function() {},//在离开黄灯状态时触发

  7. }

скопировать код

Если нужно отслеживать время жизненного цикла, нам нужно объявить его в методах и передать в соответствующей операции

Кроме того, эта библиотека функций также поддерживает множество мощных функций, таких как асинхронность, отказоустойчивость и т. д. Конкретное использование очень хорошо написано автором на github, поэтому я не буду здесь многословен.

лень - это...


Анализ исходного кода

Свет не будет работать, вниз исходный код, давайте посмотрим на библиотеку функций этой чрезвычайно загруженной модели конечного автомата, каков процесс?

Согласно вышеуказанному примеру, давайте перейдем к исходному коду, чтобы увидеть, что происходит:

  1. var fsm = new StateMachine({

  2.    init: 'green',

  3.    transitions: [

  4.      { name: 'warn',  from: 'green',  to: 'yellow' },

  5.      { name: 'panic', from: 'yellow', to: 'red'    },

  6.      { name: 'calm',  from: 'red',    to: 'yellow' },

  7.      { name: 'clear', from: 'yellow', to: 'green'  }

  8.    ]

  9.  })

скопировать код

Сначала посмотрите на эту часть, процесс создания экземпляра конечного автомата StateMachine в конструкторе StateMachine:

  1. function StateMachine(options) {

  2.  return apply(this || {}, options);//--下面进入这个方法里--

  3. }

  4. //下面是StateMachine的一些基础设置

  5. StateMachine.version  = '3.0.1';

  6. StateMachine.factory  = factory;

  7. StateMachine.apply    = apply;

  8. StateMachine.defaults = {

  9.  wildcard: '*',

  10.  init: {

  11.    name: 'init',

  12.    from: 'none'

  13.  }

  14. }

скопировать код

options — это объект инициализации конечного автомата, который мы передали:

  1. {

  2.    init: 'green',

  3.    transitions: [

  4.      { name: 'warn',  from: 'green',  to: 'yellow' },

  5.      { name: 'panic', from: 'yellow', to: 'red'    },

  6.      { name: 'calm',  from: 'red',    to: 'yellow' },

  7.      { name: 'clear', from: 'yellow', to: 'green'  }

  8.    ]

  9.  }

скопировать код

Пользовательский метод применения, передаваемый в контексте текущего конечного автомата и указанного выше объекта данных инициализации:

  1. function apply(instance, options) {

  2.  var config = new Config(options, StateMachine);//--下面进入这个方法里--

  3.  build(instance, config);

  4.  instance._fsm();

  5.  return instance;

  6. }

скопировать код

Config входными параметрами являются данные инициализации и метод конструктора StateMachine:

  1. function Config(options, StateMachine) {

  2.  options = options || {};

  3.  this.options     = options;  //初始化配置

  4.  this.defaults    = StateMachine.defaults; //状态机的基本设置

  5.  this.states      = []; //状态列表

  6.  this.transitions = []; //过渡动作列表

  7.  this.map         = {}; //状态和动作的映射

  8.  //-------------------------------------

  9.  this.lifecycle   = this.configureLifecycle();

  10.   this.configureTransitions(options.transitions || []);

  11.  //------------------------------------停,我们看上面两行代码做了什么

  12.  this.init        = this.configureInitTransition(options.init);

  13.  this.map[this.defaults.wildcard] = {};

  14.  this.data        = this.configureData(options.data);

  15.  this.methods     = this.configureMethods(options.methods);

  16. }

  17. //mixin函数就是将接收到的除第一个之外的参数对象上的属性拷贝到第一个对象上

  18. mixin(Config.prototype, {

  19. //主要是初始化构造状态机一些属性的工具方法,具体实现这里不做展示

  20.  addState: function(name) {},

  21.  addStateLifecycleNames: function(name) {},

  22.  addTransition: function(name) {},

  23.  addTransitionLifecycleNames: function(name) {},

  24.  mapTransition: function(transition) {},

  25.  //......

  26. });

  27. module.exports = Config;

скопировать код

This.lifecycle хранит несколько значений времени, которые необходимо отслеживать в основном жизненном цикле конечного автомата, а именно: on, onBefore, onAfter, onEnter, onLeave.

Время, относящееся к поведению TRAISITION - onBefore (до возникновения поведения), onAfter (после возникновения поведения)

Время, которое принадлежит состоянию STATE - onEnter (перед входом в состояние), onLeave (перед выходом из состояния)

Момент, принадлежащий и состоянию, и действию — на

this.configureLifecycle() определяет базовую структуру жизненного цикла, а this.configureTransitions() регистрирует метод жизненного цикла времени отклика для каждого состояния и действия конечного автомата.

Давайте посмотрим на жизненный цикл после завершения инициализации, и все поймут Сравните все функции жизненного цикла, перечисленные в приведенном выше использовании, чтобы понять

Ну тогда вернитесь в Config и посмотрите вниз

  1. function Config(options, StateMachine) {

  2.   //...省略上面

  3.  this.init        = this.configureInitTransition(options.init);

  4.  this.map[this.defaults.wildcard] = {};

  5.  //------------------------------------停,我们看这两行代码做了什么

  6.  this.data        = this.configureData(options.data);//传入初始化的一些数据,本例中没有,返回{}

  7.  this.methods     = this.configureMethods(options.methods);//传入自定义的一些数据,本例中没有,返回{}

  8. }

скопировать код

This.map хранит список действий, соответствующих всем объектам в машине состояний, что можно понимать как Actions in Contra, но здесь состояние соответствует не поведению, а объекту, и этот объект содержит все состояния Имя действия, которое может быть запущено. Значение, соответствующее имени, является описанием перехода, который происходит, когда происходит действие:

name — поведение, которое должно произойти, to — состояние, в которое происходит переход после возникновения поведения, active — поведение, которое должно произойти в текущем автомате

Мы видим, что none — это не данные, которые мы инициализируем и регистрируем, и init — это не поведение нашей регистрации.

На самом деле реальное начальное состояние конечного автомата по умолчанию — несуществующее «none», а поведение, соответствующее переходу из этого состояния в реальное состояние инициализации (зеленый цвет), — это метод init внутри конечного автомата.

Конечный автомат теперь собирается перейти из "none" в начальное состояние зеленого цвета, которое мы действительно установили, метод configureInitTransition будет описывать объект описания поведения, которое должно произойти с текущим конечным автоматом {name: "init", from: " none", to: "green" ", active: true} возвращает this.init

this.map[this.defaults.wildcard] = {}Зарегистрируйте произвольное состояние на карте *, вернитесь к приведенному выше коду, чтобы увидеть последние два предложения конфигурации, а затем вернитесь, чтобы применить, чтобы продолжить:

  1. function apply(instance, options) {

  2.  //instance是我们的状态机的上下文,options是传入的初始化的数据

  3.  var config = new Config(options, StateMachine);

  4.  build(instance, config);//进入下面build方法

  5.  instance._fsm();

  6.  return instance;

  7. }

  8. function build(target, config) {

  9. //这里只列举了核心代码

  10.  mixin(target, PublicMethods);//PublichMethods是用法中状态机提供给我们的工具方法is,can,cannot......

  11.  mixin(target, config.methods);//config.methods是上面列举的状态机内部的工具方法

  12.  //------------------------------------

  13.  config.allTransitions().forEach(function(transition) {

  14.    target[camelize(transition)] = function() {

  15.      return this._fsm.fire(transition, [].slice.call(arguments))

  16.    }

  17.  });

  18. //------------------------------------看下这段代码

  19.  target._fsm = function() {

  20.    this._fsm = new JSM(this, config);

  21.    this._fsm.init(arguments);

  22.  }

  23. }

скопировать код

config.allTransitions() — это список всех имен поведения конечного автомата ["init", "теплый", "паника", "спокойный", "очистить"], пройдите этот массив и зарегистрируйте поведение как метод на Государственный аппарат

Возвращаемое значение этих методов — это this._fsm.fire, о котором мы поговорим позже, вернемся к сборке:

  1. function build(target, config) {

  2. //省略上面

  3.  target._fsm = function() {

  4.    this._fsm = new JSM(this, config);

  5.    this._fsm.init(arguments);

  6.  }

  7. }

скопировать код

Зарегистрируйте метод fsm для конечного автомата.После выполнения сборки вернитесь к применению и начните выполнение метода fsm:

  1. function apply(instance, options) {

  2.  //省略上面

  3.  instance._fsm();//开始执行_fsm()

  4.  return instance;

  5. }

скопировать код

Метод fsm this.fsm = new JSM(this, config) сначала создает экземпляр JSM реального конечного автомата.

  1. function JSM(context, config) {

  2.  this.context   = context;

  3.  this.config    = config; //上面config初始化的一堆数据

  4.  this.state     = config.init.from; //none

  5.  this.observers = [context];

  6. }

  7. mixin(JSM.prototype, {

  8.  //省略实现细节,用到的下面会详细说

  9.  init: function(args) {},

  10.  is: function(state) {},

  11.  isPending: function() {},

  12.  can: function(transition) {},

  13.  allTransitions: function() {},

  14.  transitions: function() {},

  15.  seek: function(transition, args) {},

  16.  fire: function(transition, args) {},

  17.  //......

  18. });

  19. module.exports = JSM;

скопировать код

Вернемся к методу fsm, следующим шагом будет настоящая функция инициализации, this.fsm.init(arguments)

  1. mixin(JSM.prototype, {

  2.  //...上面定义的一堆方法

  3.  init: function(args) {

  4.   //只贴核心代码

  5.    if (this.config.init.active)

  6.        return this.fire(this.config.init.name, []);//----进入下面fire方法

  7.  },

  8.  fire: function(transition, args) {

  9.    return this.transit(transition, this.state, this.seek(transition, args), args);

  10.    //-----seek方法就是返回this.map[this.state][transition].to,就是当前状态none中的当前要发生行为init的行为描述中该行为过渡到的下一个状态,对照下上面的this.map,这里返回的是green

  11.    //进入下面transit方法

  12.  },

  13. transit: function(transition, from, to, args) {

  14.    //只贴核心代码

  15.    var lifecycle = this.config.lifecycle;

  16.    this.beginTransit();//方法就是将this.pending置为true,表示状态机执行中

  17.    args.unshift({            

  18.      transition: transition,

  19.      from:       from,

  20.      to:         to,

  21.      fsm:        this.context

  22.    });

  23.    //------------------------------------

  24.    return this.observeEvents([

  25.                this.observersForEvent(lifecycle.onBefore.transition),

  26.                this.observersForEvent(lifecycle.onBefore[transition]),

  27.                this.observersForEvent(lifecycle.onLeave.state),

  28.                this.observersForEvent(lifecycle.onLeave[from]),

  29.                this.observersForEvent(lifecycle.on.transition),

  30.                  [ 'doTransit', [ this ] ],

  31.                this.observersForEvent(lifecycle.onEnter.state),

  32.                this.observersForEvent(lifecycle.onEnter[to]),

  33.                this.observersForEvent(lifecycle.on[to]),

  34.                this.observersForEvent(lifecycle.onAfter.transition),

  35.                this.observersForEvent(lifecycle.onAfter[transition]),

  36.                this.observersForEvent(lifecycle.on[transition])

  37.    ], args);

  38.    //------------------------------------看下这段代码

  39.  },

  40.  //...下面定义的一堆方法

  41. }

скопировать код

МетодObserversForEvent возвращает массив, первый объект — это имя функции жизненного цикла, соответствующей текущему действию.

Второй параметр — это массив, каждый элемент представляет собой конечный автомат, в котором зарегистрирован этот метод жизненного цикла. В более сложных сценариях с несколькими конечными автоматами будет возвращен массив с длиной больше единицы. В этом примере нам нужно только обратите внимание на длину массива 0 То же, что и 1, 0 означает, что текущий конечный автомат данного метода жизненного цикла не зарегистрирован и не нуждается в выполнении, 1 означает, что конечный автомат зарегистрирован, тогда конечный автомат возвращается в массив как первый элемент массива

Третий параметр истинен, и методObserversForEvent не будет слишком заморачиваться.Посмотрим на входные параметры, которые на самом деле получаетobservEvents:

events: 

Потому что, когда мы инициализировали конечный автомат, не было прописано ни одного метода, поэтому все методы onXXX, второй параметр — пустой массив, а метод doTransit особенный, его нужно выполнять по умолчанию для перехода состояния, поэтому его второй параметр — это массив длины один, содержащий текущий конечный автомат

args построены в приведенном выше коде:

                                            
  1. mixin (JSM. prototype, {

  2.   //...上面定义的一堆方法

  3.  observeEvents : function (events, args, previousEvent , previousResult ) {

  4.     //贴核心代码

  5.     var event     = events[ 0][0 ],

  6.    args [0]. event = event;

  7.     if ( observers.length === 0 ) {

  8.       //如果这个生命周期方法该状态机没有注册,不用执行,执行下一个方法去

  9.      events .shift();

  10.       return this .observeEvents( events, args , event , previousResult );

  11.     }

  12.     else {

  13.     //敲黑板了,只有doTransit方法进来了

  14.       var observer = observers.shift (),

  15.          result = observer[ event].apply (observer, args);

  16.           //上面这句是为了把args作为第二个参数传入doTransit方法并执行,看下面doTransit方法

скопировать код

Приведенные выше два предложения будут напрямую перезаписывать 0-й элемент, выполняемый в настоящее время в массиве событий, список конечного автомата очищается, третий параметр возвращает неопределенное значение, а текущее событие становится ["doTransit", Array(0)]

  1.      if (result && typeof result.then === 'function') {

  2.        //注册的方法返回一个promise的情况

  3.        return result.then(this.observeEvents.bind(this, events, args, event)).catch(this.failTransit.bind(this))

  4.      }

  5.      else if (result === false) {

  6.        //执行完毕或者中途执行出错返回false

  7.        return this.endTransit(false);

  8.      }

  9.      else {

  10.        //影响不只一个状态机时,当前状态机从第二个参数里删掉,继续执行下一个状态机里这个方法

  11.        return this.observeEvents(events, args, event, result);

  12.      }

  13.    }

  14.  },

  15.  doTransit:    function(lifecycle) { this.state = lifecycle.to; //状态机过渡到下一个状态},

  16.   //...下面定义的一堆方法

  17. }

скопировать код

Таким образом, инициализация конечного автомата завершена, мы вручную выполняем действие, чтобы увидеть:

  1. fsm.warn()

скопировать код

Как мы сказали выше, метод действия на самом деле возвращает метод огня:

  1. return this._fsm.fire(transition, [].slice.call(arguments))

скопировать код

Тот же процесс, что и вышеприведенный метод инициализации триггера огня, потому что нет зарегистрированного метода слушателя, выполняется только метод doTransit.

Давайте зарегистрируем метод слушателя для выполнения и посмотрим:

  1. var fsm = new StateMachine({

  2.    init: 'green',

  3.    transitions: [

  4.      { name: 'warn',  from: 'green',  to: 'yellow' },

  5.      { name: 'panic', from: 'yellow', to: 'red'    },

  6.      { name: 'calm',  from: 'red',    to: 'yellow' },

  7.      { name: 'clear', from: 'yellow', to: 'green'  }

  8.    ],

  9.    methods: {

  10.      onBeforeWarn:  function() { console.log("执行warn,state将变为yellow")                },

  11.    }

  12.  })

  13. fsm.warn();  

  14.  console.log(fsm.state)

скопировать код

Давайте посмотрим непосредственно на события первого параметра методаObserverEents:

Вывод консоли:

Конечный автомат - это такой основной процесс в целом, очень простой и очень простой пример, желающие понять, студенты могут пойти посмотреть на источник крупного рогатого скота.

Суммировать

Я вижу, что это не так просто, я рухнул, нужно подвести итог, то это не слишком большая проблема, сцены не используют этот конечный автомат библиотеки, они знают, что основная идея состоит в том, чтобы добиться отличного результата, но на самом деле нужно использовать эта функция, если поддерживается библиотечной функцией, очень мощная, вы можете пойти и посмотреть код автора и документацию -

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