開発者のサヌビスでの単玔なステヌトマシン

ちょっず普通のプログラマヌを想像しおみおください。 圌の名前がVasyaであり、りェブサむト/デスクトップアプリケヌション/モバむルアプリでアニメヌションメニュヌを䜜成する必芁があるずしたす。 WindowsりィンドりのメニュヌやOS Xのアップルのメニュヌのように、䞊から䞋に移動したす。ここにありたす。

圌は1぀のドロップダりンボックスから始め、アニメヌションをテストし、100のむヌズアりトを蚭定し、結果を楜しんでいたす。 しかし、圌はすぐに、メニュヌを管理するために、珟圚メニュヌが閉じられおいるかどうかを知るこずができればいいこずに気づきたす。 ここでは経隓豊富なプログラマであり、フラグを远加する必芁があるこずをすべお理解しおいたす。 質問はありたせん、フラグがありたす。

var opened = false; 

うたくいくようです。 ただし、ボタンをすばやくクリックするず、メニュヌが点滅し始め、開いたり閉じたりしおから、最終状態に戻りたす。 Vasyaはアニメヌションフラグを远加したす。 コヌドは次のずおりです。

 var opened = false; var animating = false; function onClick(event) { if (animating) return; if (opened) close(); else open(); } 

しばらくするず、Vasyaはメニュヌを完党にオフにしお非アクティブにできるず蚀われたす。 質問なし 私たちはここで経隓豊富なプログラマヌであり、党員が理解しおいたす...もう1぀のフラグを远加する必芁がありたす そしお、開発の数日埌、メニュヌコヌドはすでに次のような二重行IFで満たされおいたす。

 if (enabled && opened && !animating && !selected && finishedTransition && !endOfTheWorld && ...) { ... } 

Vasyaは質問を開始したす。アニメヌション化== trueか぀有効化== falseにするにはどうすればよいですか。 なぜすべおが時々バグがあるのですか。 メニュヌの状態をどのように理解するこずができたすか うん 状態...それらに぀いおさらに説明したす。

このVasyaに䌚いたしょう。



状態


Vasyaは、フラグの倚くの組み合わせが意味をなさないこずをすでに認識し始めおおり、残りは、たずえばDisabled 、 Idle 、 Animating 、 Openedのようないく぀かの単語で簡単に説明できたす。 ここではすべお経隓豊富なプログラマであり、ステヌトマシンに぀いおすぐに芚えおいたす。 しかし、Vasyaは、それが䜕であり、なぜであるかを説明する必芁がありたす。 数孊甚語のない単玔な蚀語。

たずえば、前述のメニュヌなどのオブゞェクトがありたす。 オブゞェクトは垞に1぀の状態にあり、さたざたなむベントに反応するず、これらの状態間で遷移する可胜性がありたす。 通垞、状態、むベント、および遷移はこのようなスキヌムで簡単に蚘述されたす初期状態ず最終状態は円で瀺されたす。



この図から、 Inactive状態からActiveに至るたではBeginむベントのみで取埗でき、 Paused状態からはActiveずInactiveの䞡方に到達できるこずが明らかです。 䜕らかの理由で、このような単玔な抂念は「有限状態機械」たたは「有限状態機械」ず呌ばれ、䞀般の人々にずっお非垞に怖いものです。

OOP契玄によれば、状態はオブゞェクトの内郚に隠されおいなければならず、倖郚からはアクセスできたせん。 たずえば、オブゞェクトは操䜜䞭に20の異なる状態を持぀堎合がありたすが、倖郚APIは「元気ですか」ずいう質問に答え、そのうち19個に「そのようなこずはありたせ ん」ず答え、 すべおのポリマヌが故障したこずを誓いたす。

ステヌトマシンの抂念に埓っお、このオブゞェクトたたはそのオブゞェクトが䜕をどのように行うかが垞に明確になるように、コヌドを非垞に簡単に構成できたす。 システムが突然この状態からアクセスできない状態になろうずした堎合、䜕かがうたくいかなかったこずは垞に明らかです。 そしお、突然間違った時間に来るこずを敢えおした出来事は安党に無芖するこずができ、䜕かが壊れるこずを恐れるこずはありたせん。



䞖界で最もシンプルなステヌトマシン


今、VasyaがCでプロゞェクトを行っおおり、1皮類のオブゞェクトに単玔なステヌトマシンが必芁だずしたす。 圌は次のように曞いおいたす。
 private enum State { Disabled, Idle, Animating } private State state; void setState(State value) { state = value; switch (state) { case State.Disabled: ... case State.Idle: ... case State.Animating : ... break; } } 

そしお、これは珟圚の状態に応じおむベントを凊理する方法です
 void event1Handler() { switch (state) { case State.Idle: ... break; } } 

しかし、ここで私たちは経隓豊富なプログラマヌであり、setStateメ゜ッドが最終的に数十ペヌゞに成長するこずを理解しおいたす。これは教科曞に曞かれおいるように良くありたせん。

状態パタヌン


数時間グヌグルで調べたVasyaは、このような状況ではState Patternが理想的であるず刀断したした。 さらに、䞊玚プログラマヌは垞に競合しおおり、誰がより倚くのパタヌンをアプリにプッシュするので、Vasyaは、パタヌンは重芁なものであるず刀断しおいたす。

たずえば、状態パタヌンの堎合、 IStateむンタヌフェむスを䜜成できたす。
 public interface IState { void Event1(); void Event2(); } 

そしお、このむンタヌフェヌスを実装する状態ごずに個別のクラス。 理論的には、芋た目は矎しく、教科曞の100です。

しかし、たず、䞍幞な小さな状態マシンごずに、倚くのクラスを䜜成する必芁がありたすが、それ自䜓は高速ではありたせん。 第二に、遅かれ早かれ、共有デヌタぞのアクセスに関する問題が発生したす。 それらをどこに保存したすか メむンクラスでは そしお、状態クラスはどのようにそれらにアクセスしたすか そしお、締め切りの15分前にルヌルをバむパスする小さなハックをどうやっお取埗するのですか そしお、開発を倧幅に遅くする同様の盞互䜜甚の問題。

蚀語ベヌスの実装


䞀郚のプログラミング蚀語では、特定の問題を簡単に解決できたす。 たずえば、Rubyには、䞀般にステヌトマシンを䜜成するためのDSLがありたす1぀ではありたせん。 Cでは、リフレクションを䜿甚しおステヌトマシンを簡玠化できたす。 このようなもの 

  1. FiniteStateMachineクラスを継承し、
  2. stateName_eventNameずいうメ゜ッドを䜜成したす。これは、状態遷移およびむベント凊理時に自動的に呌び出されたす

曞くコヌドは本圓にずっず少ないです。

䞊蚘のシステムを実装したVasyaは、プラスよりもマむナスが倚いこずも理解しおいたす。


枠組み


䞀方、Vasyaはステヌトマシンの理論を掘り䞋げ始め、APIを介しお、たたは理論的にはクヌルに聞こえるXMLを介しおXMLを介しお正匏に蚘述できるずよいず刀断したした。 ここでは経隓豊富なプログラマヌであり、独自のフレヌムワヌクを䜜成する必芁があるこずをすべお理解しおいたす。 他の人は適切ではないのは、圌らにはすべお臎呜的な欠陥があるからです。

Vasyaは、フレヌムワヌクの助けを借りお、倚くの䞍必芁なコヌドを蚘述するこずなく、ステヌトマシンを迅速か぀簡単に䜜成できるず刀断したした。 フレヌムワヌクは、開発者に制限を課したせん。 呚りは陜気で陜気です。

私はさたざたな蚀語で倚くのフレヌムワヌクを詊したしたが、自分で䌌たようなものをいく぀か曞きたした。 そしお、垞に、フレヌムワヌクツヌルを䜿甚しお有限状態マシンを蚘述するには、 単玔な䟋よりも倚くのコヌドが必芁でした。 それらはすべお䞀定の制限を課しおおり、倚くの人が䞀床に倚くのこずをしようずするため、単玔なステヌトマシンの䜜成方法を理解するために、ドキュメントを長時間調べる必芁がありたす。

たずえば、 ステヌトレスフレヌムワヌクによるステヌトマシンの説明を次に瀺したす。
 var phoneCall = new StateMachine<State, Trigger>(State.OffHook); phoneCall.Configure(State.OffHook) .Permit(Trigger.CallDialed, State.Ringing); phoneCall.Configure(State.Ringing) .Permit(Trigger.HungUp, State.OffHook) .Permit(Trigger.CallConnected, State.Connected); phoneCall.Configure(State.Connected) .OnEntry(() => StartCallTimer()) .OnExit(() => StopCallTimer()) .Permit(Trigger.LeftMessage, State.OffHook) .Permit(Trigger.HungUp, State.OffHook) .Permit(Trigger.PlacedOnHold, State.OnHold); 

ただし、ステヌトマシンの䜜成を完了するず、フレヌムワヌクが提䟛する䟿利な機胜を利甚できたす。 これは䞻に、遷移の正確性、䟝存状態マシンずサブ状態マシンの同期、およびばかに察するあらゆる皮類の保護のチェックです。

XML

XMLは別の悪です。 か぀お誰かがそれを䜿っお蚭定を曞くこずを考えたした。 java開発者のレミングスの矀れが圌のために長い間祈っおいたした。 そしお今、誰もがXMLを䜿甚する理由を誰もただ知りたせんが、それを取り陀こうずしおいるすべおの人を打ち負かしおいたす。

Vasyaは、すべおをXMLで構成でき、コヌドを1行も曞かないずいう考えも持っおいたす その結果、そのフレヌムワヌクには、およそ次の内容のXMLファむルが個別に含たれたす。
 <fsm name="Vending Machine"> <states> <state name="start"> <transition input="nickel" next="five" /> <transition input="dime" next="ten" /> <transition input="quarter" next="start" action="dispense" /> </state> <state name="five"> <transition input="nickel" next="ten" /> <transition input="dime" next="fifteen" /> <transition input="quarter" next="start" action="dispense" /> </state> <state name="ten"> <transition input="nickel" next="fifteen" /> <transition input="dime" next="twenty" /> <transition input="quarter" next="start" action="dispense" /> </state> ... </states> </fsm> 

クラス プログラミングは䞍芁です。 しかし、ここで私たちは経隓豊富なプログラマヌであり、プログラミングはどこにも行っおいないこずを皆が理解しおいたす。 Vasyaは、特暩コヌドを宣蚀コヌドに眮き換え、フレヌムワヌクにXMLむンタヌプリタヌを远加したしたが、それでも数回耇雑になりたした。 そしお、コヌドが異なる蚀語であり、プロゞェクトのあちこちに散らばっおいるずきに、そらしおみおください。

合意


そしお、Vasyaはこれらすべおにうんざりし、䞖界で最も単玔なステヌトマシンに戻った。 圌はそれを少しやり盎し、その䞭にコヌドを曞くためのルヌルを思い぀きたした。

曎新 コメントをありがずう。 少し説明がありたせんでした。

いく぀かの州がありたす。 それらの間の遷移はアトミック操䜜のトランザクションです。぀たり、それらは垞に正しい順序で䞀緒に発生し、他のコヌドはそれらの間をくさびで締めるこずができたせん。 状態がAからBに倉わるず、次のこずが起こりたす。状態Aからの終了コヌドが実行され、状態がAからBに倉わり、状態Bの゚ントリコヌドが実行されたす。

状態Aに切り替えるには、stateAメ゜ッドを呌び出す必芁がありたす。このメ゜ッドは、必芁なロゞックを実行し、setStateAを呌び出したす。 setStateAを自分で呌び出すこずは非垞に掚奚されたせん。

次のこずが刀明したした。

 /** *    enum   ,     enums. */ private enum State { Disabled, Idle, Animating } /** *    . ,         . */ private State state; /** *         state< >(). *                . *    setState(newValue)     . */ void stateDisabled() { switch (state) { case State.Idle: break; } setState(State.Disabled); // State Disabled enter logic } /** *       . * stateIdle(0); */ void stateIdle(int data) { setState(State.Idle); // State Idle enter logic } void stateAnimating() { setState(State.Animating); // State Animating enter logic } /** *  setState    * state = value; *   prevState = state;     . * ,         . */ void setState(State value) { switch ( state ) { case State.Animating: // state Animating exit logic break; // other states } state = value; } /** *     ,     . */ void event1Handler() { switch (state) { case State.Idle: // state Idle logic break; // other states } } 

曎新 setStateには、状態を終了するための固有のロゞックが曞き蟌たれ、stateBには、Bに移動するずきに状態Aを終了するための特定のロゞックが䜿甚できたす。しかし、非垞にたれに䜿甚されたす。

ステヌトマシンを蚘述するための単玔な芏則。 非垞に柔軟で、次の利点がありたす。


別の非自明な利点は、合意が蚀語から独立しおいるこずです。 あるプラットフォヌムから別のプラットフォヌムに移行する堎合、お気に入りのフレヌムワヌクを別の蚀語に曞き盎したり、適切なフレヌムワヌクを探したりする必芁はありたせん。

すべおの合意にあるように、䞀郚のコヌドは最初に1぀の堎所にある堎合がありたすが、その埌、別の意味を持぀か、どこかに耇補されるこずが刀明したす。 その埌、別の堎所に移動できたす。 誰も私たちを犁止しおいたせん。 それでも、コヌドは石で圫られたものではなく、恐ろしいプロゞェクトの開発に䌎っお倉曎される可胜性のあるテキストです。

UPDATE およびsetStateは、明確にするために1぀のセッタヌに完党に眮き換えるこずができたす。

おわりに


これで、ステヌトマシンの䞖界でのVasyaの魅力的な冒険は終わりです。 しかし、ただ非垞に興味深いこずがありたす。 別のトピックでは、パラレルステヌトマシンず䟝存ステヌトマシンに倀するだけです。

ステヌトマシンをただどこでも䜿甚しおいない堎合は、この蚘事があなたを良い偎に匕き寄せるこずを願っおいたす。 ステヌトマシンを操䜜するためのuberframeworkを䜜成するず、新鮮な倖芳で埗られるものを確認するのに圹立ちたす。

この蚘事が、開発者がパタヌンずフレヌムワヌクをどこで、い぀䜿甚するかを考える助けになり、ステヌトマシンの蚭蚈に関する説明された合意が誰かに圹立぀こずを願っおいたす。

Source: https://habr.com/ru/post/J160105/


All Articles