Počítače Windows Internet

Štát. Štátne triedy pre každý štát

Behaviorálny dizajnový vzor. Používa sa v prípadoch, keď počas vykonávania programu musí objekt zmeniť svoje správanie v závislosti od jeho stavu. Klasická implementácia zahŕňa vytvorenie základnej abstraktnej triedy alebo rozhrania obsahujúceho všetky metódy a jednu triedu pre každý možný stav. Vzor je špeciálnym prípadom odporúčania „nahradiť podmienené príkazy polymorfizmom“.

Zdalo by sa, že všetko je podľa knihy, ale je tu nuansa. Ako správne implementovať metódy, ktoré nie sú pre daný štát relevantné? Ako napríklad odstrániť položku z prázdneho košíka alebo zaplatiť za prázdny košík? Každá trieda stavu zvyčajne implementuje iba relevantné metódy a v iných prípadoch vyvolá výnimku InvalidOperationException.

Porušenie zásady nahradenia osoby Liskovom. Yaron Minsky navrhol alternatívny prístup: urobiť nelegálne štáty nereprezentovateľnými. To umožňuje presunúť kontrolu chýb z runtime do doby kompilácie. Riadiaci tok však bude v tomto prípade organizovaný na základe zhody vzorov a nie pomocou polymorfizmu. Našťastie, .

Viac podrobností o príklade témy F# urobiť nelegálne štáty nereprezentovateľnými odhalila webová stránka Scotta Vlashina.

Uvažujme o implementácii „štátu“ na príklade koša. C# nemá vstavaný typ únie. Oddeľme dáta a správanie. Samotný stav zakódujeme pomocou enum a správanie ako samostatnú triedu. Pre pohodlie deklarujme atribút spájajúci enum a zodpovedajúcu triedu správania, základnú triedu „stav“ a pridajte metódu rozšírenia na prechod z enum do triedy správania.

Infraštruktúra

public class StateAttribute: Atribút ( public Type StateType ( get; ) public StateAttribute (Type stateType) ( StateType = stateType ?? throw new ArgumentNullException(nameof(stateType)); ) ) public abstract class State kde T: trieda ( chránený stav (entita T) ( entita = entita ?? hodiť novú výnimku ArgumentNullException (meno(entity)); ) chránená entita T ( get; ) ) verejná statická trieda StateCodeExtensions ( verejný statický stav ToState (tento Enum stateCode, objektová entita) kde T: trieda // áno, áno odraz je pomalý. Nahraďte kompilovaným stromom výrazov // alebo IL Emit a bude to rýchle => (State ) Activator.CreateInstance(stateCode .GetType() .GetCustomAttribute ().StateType, entita); )

Predmetná oblasť

Vyhlásime entitu „košík“:

Verejné rozhranie IHasState kde TEntity: class ( TStateCode StateCode ( get; ) State State ( get; ) ) verejná čiastočná trieda Košík: IHasState ( public User User ( get; protected set; ) public CartStateCode StateCode ( get; protected set; ) public State State => StateCode.ToState (toto); public decimal Total ( get; protected set; ) chránená virtuálna ICollection Produkty (získať; nastaviť; ) = nový zoznam (); // Iba ORM chránený Cart() ( ) public Cart(User user) ( User = user ?? throw new ArgumentNullException(nameof(user)); StateCode = StateCode = CartStateCode.Empty; ) public Cart(User user, IEnumerable Products) : this(user) ( StateCode = StateCode = CartStateCode.Empty; foreach (var product in products) ( Products.Add(product); ) ) public Cart(User user, IEnumerable Produkty, desatinný súčet) : toto (používateľ, produkty) ( ak (celkom<= 0) { throw new ArgumentException(nameof(total)); } Total = total; } }
Implementujeme jednu triedu pre každý stav košíka: prázdny, aktívny a zaplatený, ale nebudeme deklarovať spoločné rozhranie. Nech každý štát implementuje len relevantné správanie. To neznamená, že triedy EmptyCartState, ActiveCartState a PaidCartState nemôžu všetky implementovať rovnaké rozhranie. Môžu, ale takéto rozhranie musí obsahovať len metódy, ktoré sú dostupné v každom štáte. V našom prípade je metóda Add dostupná v EmptyCartState a ActiveCartState, takže ich môžeme zdediť z abstraktného AddableCartStateBase. Položky však môžete pridávať len do nezaplateného košíka, takže nebude existovať spoločné rozhranie pre všetky štáty. Týmto spôsobom garantujeme, že v našom kóde v čase kompilácie nie je žiadna InvalidOperationException.

Verejná čiastočná trieda Cart ( public enum CartStateCode: bajt ( Empty, Active, Pay ) verejné rozhranie IAddableCartState ( ActiveCartState Add(produkt produktu); IEnumerable Produkty ( get; ) ) verejné rozhranie INotEmptyCartState ( IEnumerable Produkty ( get; ) desiatkové Celkom ( get; ) ) verejná abstraktná trieda AddableCartState: State , IAddableCartState ( chránené AddableCartState(entita košíka): základ(entita) ( ) verejné ActiveCartState Pridať(produkt produktu) ( Entity.Products.Add(produkt); Entity.StateCode = CartStateCode.Active; vrátiť (ActiveCartState)Entity.State; ) verejné IEpočetné Produkty => Entity.Products; ) verejná trieda EmptyCartState: AddableCartState ( public EmptyCartState(entita košíka): základňa(entita) ( ) ) verejná trieda ActiveCartState: AddableCartState, INotEmptyCartState (verejná ActiveCartState(entita košíka): základ(entita) ( ) verejné ZaplatenáCartState (desať) Entity.Total = súčet; Entity.StateCode = CartStateCode.Paid; return (PaidCartState)Entity.State; ) verejný štát Remove(Product product) ( Entity.Products.Remove(product); if(!Entity.Products.Any()) ( Entity.StateCode = CartStateCode.Empty; ) return Entity.State; ) public EmptyCartState Clear() ( Entity. Products.Clear(); Entity.StateCode = CartStateCode.Empty; return (EmptyCartState)Entity.State; ) public decimal Total => Products.Sum(x => x.Price); ) verejná trieda PaidCartState: Štát , INotEmptyCartState ( public IEnumerable Produkty => Entity.Products; verejné desiatkové Celkom => Entita.Celkom; public PaidCartState(entita košíka) : základ (entita) ( ) ) )
Štáty sú vyhlásené za vnorené ( vnorené) triedy nie sú náhodné. Vnorené triedy majú prístup k chráneným členom triedy Cart, čo znamená, že pri implementácii správania nemusíme obetovať zapuzdrenie entity. Aby som sa vyhol neporiadku v súbore triedy entity, rozdelil som deklaráciu na dve časti: Cart.cs a CartStates.cs pomocou kľúčového slova čiastočné.

Public ActionResult GetViewResult(State cartState) ( switch (cartState) ( case Cart.ActiveCartState activeState: return View("Active", activeState); case Cart.EmptyCartState emptyState: return View("Empty", emptyState); case Cart.PaidCartState paidCartState: return View(" Paid", paidCartState); predvolené: hodiť novú InvalidOperationException(); ) )
V závislosti od stavu vozíka použijeme rôzne zobrazenia. V prípade prázdneho košíka zobrazíme správu „váš košík je prázdny“. Aktívny košík bude obsahovať zoznam produktov, možnosť zmeniť počet produktov a niektoré z nich odstrániť, tlačidlo „objednať“ a celkovú sumu nákupu.

Zaplatený košík bude vyzerať rovnako ako aktívny košík, ale bez možnosti čokoľvek upravovať. Túto skutočnosť je možné zaznamenať zvýraznením rozhrania INotEmptyCartState. Tým sme sa zbavili nielen porušenia princípu Liskovovej substitúcie, ale aplikovali sme aj princíp oddelenia rozhrania.

Záver

V kóde aplikácie môžeme pracovať s odkazmi rozhrania IAddableCartState a INotEmptyCartState na opätovné použitie kódu zodpovedného za pridávanie položiek do košíka a zobrazovanie položiek v košíku. Domnievam sa, že zhoda vzorov je vhodná len na riadenie toku v C#, keď medzi typmi nie je nič spoločné. V iných prípadoch je práca so základným odkazom pohodlnejšia. Podobnú techniku ​​je možné použiť nielen na zakódovanie správania entity, ale aj na .

Je čas na priznanie: S týmto hlavným som to trochu prehnal. Malo ísť o dizajnový vzor GoF State. Nemôžem však hovoriť o jeho použití v hrách bez toho, aby som sa nedotkol konceptu konečných automatov(alebo "FSM"). Ale keď som sa do toho dostal, uvedomil som si, že si to budem musieť pamätať hierarchický stavový stroj alebo hierarchický automat A automatický stroj s pamäťou zásobníka (zásobné automaty).

Toto je veľmi široká téma, takže aby bola táto kapitola čo najkratšia, vynechám niekoľko zjavných príkladov kódu a niektoré medzery budete musieť vyplniť sami. Dúfam, že to nezníži ich zrozumiteľnosť.

Nie je potrebné sa rozčuľovať, ak ste nikdy nepočuli o konečných automatoch. Sú dobre známe vývojárom AI a počítačovým hackerom, ale málo známe v iných oblastiach. Podľa mňa si zaslúžia viac uznania, preto vám chcem ukázať niekoľko problémov, ktoré riešia.

To všetko sú ozveny starých začiatkov umelej inteligencie. V 50. a 60. rokoch sa umelá inteligencia zameriavala najmä na spracovanie jazykových štruktúr. Mnohé z technológií používaných v moderných kompilátoroch boli vynájdené na analýzu ľudských jazykov.

Všetci sme tam boli

Povedzme, že pracujeme na malej plošinovke s bočným rolovaním. Našou úlohou je vymodelovať hrdinku, ktorá bude hráčovým avatarom v hernom svete. To znamená, že musí reagovať na vstup používateľa. Stlačte tlačidlo B a ona bude skákať. Celkom jednoduché:

void Heroine::handleInput(Input input) ( if (vstup == PRESS_B) ( yVelocity_ = JUMP_VELOCITY; setGraphics(IMAGE_JUMP); ) )

Všimli ste si chybu?

Nie je tu žiadny kód, ktorý by zabránil „skákaniu do vzduchu“; stále stláčajte B, kým je vo vzduchu a bude lietať znova a znova. Najjednoduchší spôsob, ako to vyriešiť, je pridať booleovskú vlajku isJumping_ do Heroine, ktorá bude sledovať, kedy hrdinka skočila:

void Heroine::handleInput(Input input) ( if (input == PRESS_B) ( if (!isJumping_) ( isJumping_ = true ; // Skok... ) ) )

Potrebujeme tiež kód, ktorý nastaví isJumping_ späť na false, keď sa hrdinka opäť dotkne zeme. Pre jednoduchosť tento kód vynechávam.

void Heroine::handleInput(vstup vstupu) ( if (vstup == PRESS_B) ( // Poďme skočiť, ak sme tak ešte neurobili...) else if (vstup == PRESS_DOWN) ( if (!isJumping_) ( setGraphics(IMAGE_DUCK); ) ) else if (vstup == RELEASE_DOWN) ( setGraphics(IMAGE_STAND); ) )

Všimli ste si tu chybu?

Pomocou tohto kódu môže hráč:

  1. Stlačte dole, aby ste sa dostali do drepu.
  2. Stlačte B pre skok zo sedu.
  3. Uvoľnite sa vo vzduchu.

Hrdinka sa zároveň prepne na grafiku státia priamo vo vzduchu. Budeme musieť pridať ďalšiu vlajku...

void Heroine::handleInput(Input input) ( if (input == PRESS_B) ( if (!isJumping_ && !isDucking_) ( // Skok... ) ) else if (input == PRESS_DOWN) ( if (!isJumping_) ( isDucking_ = true ; setGraphics(IMAGE_DUCK); ) ) else if (vstup == RELEASE_DOWN) ( if (isDucking_) ( isDucking_ = false ; setGraphics(IMAGE_STAND); ) ) )

Teraz by bolo skvelé pridať schopnosť hrdinky zaútočiť pomocou náčinia, keď hráč stlačí vo vzduchu:

void Heroine::handleInput(Input input) ( if (input == PRESS_B) ( if (!isJumping_ && !isDucking_) ( // Skok... ) ) else if (input == PRESS_DOWN) ( if (!isJumping_) ( isDucking_ = true ; setGraphics(IMAGE_DUCK); ) else ( isJumping_ = false ; setGraphics(IMAGE_DIVE); ) ) else if (input == RELEASE_DOWN) ( if (isDucking_) ( // Stojí... ) ) )

Opäť sa hľadajú chyby. Našiel?

Máme kontrolu, aby sme znemožnili skok do vzduchu, ale nie počas zdolávania. Pridáva sa ďalšia vlajka...

S týmto prístupom nie je niečo v poriadku. Zakaždým, keď sa dotkneme kódu, niečo sa pokazí. Budeme musieť pridať veľa pohybu, ani nemáme chôdze nie, ale s týmto prístupom budeme musieť prekonať veľa chýb.

Programátori, ktorých si všetci idealizujeme a ktorí vytvárajú skvelý kód, v skutočnosti vôbec nie sú supermani. Jednoducho si vyvinuli inštinkt pre kód, ktorý hrozí zavedením chýb a snažia sa im vyhnúť, kedykoľvek je to možné.

Komplexné vetvenie a zmena stavov sú presne tie typy kódu, ktorým by ste sa mali vyhnúť.

Konečné automaty sú našou spásou

V návale frustrácie odstránite zo svojho stola všetko okrem ceruzky a papiera a začnete kresliť vývojový diagram. Nakreslíme obdĺžnik pre každú činnosť, ktorú môže hrdinka vykonať: státie, skákanie, prikrčenie a kotúľanie. Aby mohol reagovať na stlačenie klávesov v ktoromkoľvek zo stavov, nakreslíme šípky medzi tieto obdĺžniky, označíme tlačidlá nad nimi a spojíme stavy dohromady.

Gratulujeme, práve ste vytvorili štátny automat (konečný automat). Pochádzajú z oblasti informatiky tzv teória automatov (teória automatov), ktorého rodina štruktúr zahŕňa aj slávny Turingov stroj. FSM je najjednoduchším členom tejto rodiny.

Pointa je toto:

    Máme pevnú zostavu štátov, ktorý môže obsahovať guľomet. V našom príklade sú to státie, skákanie, prikrčenie a kotúľanie.

    Stroj môže byť iba v jeden v ktoromkoľvek danom čase. Naša hrdinka nemôže skákať a stáť súčasne. V skutočnosti sa FSM používa v prvom rade, aby sa tomu zabránilo.

    Následná sekvencia vstup alebo diania, prenášané do stroja. V našom príklade je to stlačenie a uvoľnenie tlačidiel.

    Každý štát má prechodová súprava, z ktorých každý je spojený so vstupom a označuje stav. Keď používateľ zadá, ak sa zhoduje s aktuálnym stavom, stroj zmení svoj stav na miesto, kde ukazuje šípka.

    Ak napríklad stlačíte v stoji, prejde do stavu podrepu. Stlačením pri skákaní zmeníte stav na zdolávanie. Ak v aktuálnom stave nie je poskytnutý žiadny prechod pre vstup, nič sa nedeje.

Vo svojej najčistejšej forme je to celý banán: stavy, vstupy a prechody. Môžete ich znázorniť vo forme blokovej schémy. Bohužiaľ, kompilátor nebude rozumieť takýmto klikihákom. Tak ako teda realizovať konečný automat? Gang of Four ponúka svoju vlastnú verziu, no začneme ešte jednoduchšou.

Moja obľúbená analógia FSM je starý textový quest Zork. Máte svet pozostávajúci z miestností, ktoré sú prepojené priechodmi. A môžete ich preskúmať zadaním príkazov ako „choď na sever“.

Takáto mapa plne zodpovedá definícii konečného automatu. Miestnosť, v ktorej sa nachádzate, je aktuálny. Každý výstup z miestnosti je prechodom. Navigačné príkazy - vstup.

Výčty a prepínače

Jedným z problémov našej starej triedy Heroine je, že umožňuje nesprávnu kombináciu booleovských kľúčov: isJumping_ a isDucking_ , nemôžu byť pravdivé súčasne. A ak máte niekoľko booleovských príznakov, z ktorých iba jeden môže byť pravdivý , nebolo by lepšie ich všetky nahradiť enum .

V našom prípade pomocou enum môžeme úplne opísať všetky stavy nášho FSM týmto spôsobom:

enum Stav ( STATE_STANDING, STATE_JUMPING, STATE_DUCKING, STATE_DIVING );

Namiesto hromady vlajok má Heroine iba jedno pole state_. Budeme musieť zmeniť aj poradie vetvenia. V predchádzajúcom príklade kódu sme najprv vetvili v závislosti od vstupu a potom od stavu. Pritom sme kód zoskupili podľa stlačeného tlačidla, ale rozmazali sme kód spojený so stavmi. Teraz urobíme opak a prepneme vstup v závislosti od stavu. Toto dostaneme:

void Heroine::handleInput(Vstupný vstup) (prepínač (stav_) ( prípad STATE_STANDING: if (vstup == STLAČTE_B) (stav_ = STAV_SKOK; yVelocity_ = SKOČNÁ_RÝCHLOSŤ; setGraphics(IMAGE_JUMP); ) else if (vstup == STLAČENIE_DOLE) = STATE_DUCKING; setGraphics(IMAGE_DUCK); ) break ; case STATE_JUMPING: if (vstup == PRESS_DOWN) ( state_ = STATE_DIVING; setGraphics(IMAGE_DIVE); ) break ; case STATE_DUCKING: if (vstup == set_RELEASE_DOWN_HSTANDING) (IMAGE_STAND); ) prestávka; ) )

Vyzerá to celkom triviálne, ale napriek tomu je tento kód už oveľa lepší ako predchádzajúci. Stále máme určité podmienené vetvenie, ale zmenili sme premenlivý stav na jediné pole. Všetok kód, ktorý spravuje jeden štát, sa zhromažďuje na jednom mieste. Toto je najjednoduchší spôsob implementácie konečného automatu a niekedy úplne postačuje.

Teraz už hrdinka nebude môcť byť in neistý stave. Pri použití booleovských príznakov boli možné niektoré kombinácie, ale nedávali zmysel. Pri použití enum sú všetky hodnoty správne.

Bohužiaľ, váš problém môže prerásť toto riešenie. Povedzme, že sme našej hrdinke chceli pridať špeciálny útok, pri ktorom si hrdinka potrebuje sadnúť, aby sa dobila a následne vybila nahromadenú energiu. A keď sedíme, musíme si dávať pozor na čas nabíjania.

Pridajte pole chargeTime_ do Heroine, aby ste uložili čas nabíjania. Povedzme, že už máme metódu update() volanú v každom rámci. Pridajme k nemu nasledujúci kód:

void Heroine::update() ( if (state_ == STATE_DUCKING) ( chargeTime_++; if (chargeTime_ > MAX_CHARGE) ( superBomb(); ) ) )

Ak ste uhádli vzor Metóda aktualizácie, vyhrali ste cenu!

Zakaždým, keď sa znova dostaneme do drepu, musíme tento časovač vynulovať. Aby sme to dosiahli, musíme zmeniť handleInput() :

void Heroine::handleInput(vstupný vstup) (prepínač (stav_) ( prípad STATE_STANDING: if (vstup == STLAČENIE_NADOL) (stav_ = STATE_DUCKING; chargeTime_ = 0 ; setGraphics(IMAGE_DUCK); ) // Spracovať zostávajúci vstup... prestávka ; // Iné štáty... } }

Nakoniec, aby sme pridali tento útok náboja, museli sme zmeniť dve metódy a pridať pole chargeTime_ do Heroine, aj keď sa používa iba v skrčenom stave. Chcel by som mať všetky tieto kódy a údaje na jednom mieste. Gang of Four nám s tým môže pomôcť.

Stav šablóny

Pre ľudí dobre oboznámených s objektovo orientovanou paradigmou je každá podmienená vetva príležitosťou na použitie dynamického odosielania (inými slovami, volanie virtuálnej metódy v C++). Myslím, že musíme ísť ešte hlbšie do tejto králičej nory. Niekedy, ak je všetko, čo potrebujeme.

Existuje na to historický základ. Mnohí zo starých apoštolov objektovo orientovanej paradigmy, ako napríklad Gang of Four a ich Programovacie vzory a Martin Fuller so svojou Refaktorovanie pochádzal zo Smalltalku. A tam ifThen je len metóda, ktorú používate na spracovanie podmienky a ktorá je implementovaná odlišne pre pravdivé a nepravdivé objekty.

V našom príklade sme už dosiahli kritický bod, kedy by sme mali venovať pozornosť niečomu objektovo orientovanému. To nás privádza k vzoru štátu. Aby som citoval Gang of Four:

Umožňuje objektom meniť svoje správanie v súlade so zmenami vnútorného stavu. V tomto prípade sa objekt bude správať ako iná trieda.

nie je to veľmi jasné. Nakoniec si s týmto poradí aj switch. Vo vzťahu k nášmu príkladu s hrdinkou by šablóna vyzerala takto:

Stavové rozhranie

Najprv definujme rozhranie pre štát. Každý kúsok správania závislého od stavu – t.j. všetko, čo sme predtým implementovali pomocou prepínača, sa zmení na virtuálnu metódu tohto rozhrania. V našom prípade sú to handleInput() a update() .

class HeroineState ( public : virtual ~HeroineState() () virtuálna void handleInput{} {} };

Triedy pre každý štát

Pre každý stav definujeme triedu, ktorá implementuje rozhranie. Jeho metódy určujú správanie hrdinky v tomto stave. Inými slovami, vezmeme všetky možnosti z prepínača v predchádzajúcom príklade a zmeníme ich na triedu stavu. Napríklad:

class DuckingState: public HeroineState ( public : DuckingState() : chargeTime_(0) () virtuálna void handleInput (Hrdinka a hrdinka, vstup)( if (vstup == RELEASE_DOWN) ( // Prechod do trvalého stavu... heroine.setGraphics(IMAGE_STAND); )) aktualizácia virtuálnej prázdnoty (hrdinka a hrdinka)( chargeTime_++; if (chargeTime_ > MAX_CHARGE) ( heroine.superBomb(); ) ) private : int chargeTime_; );

Upozorňujeme, že chargeTime_ sme presunuli z vlastnej triedy hrdinky do triedy DuckingState. A to je veľmi dobré, pretože tento údaj má význam iba v tomto stave a náš dátový model to jasne naznačuje.

Delegovanie na štát

triedna hrdinka ( verejnosť : virtuálna void handleInput (vstupný vstup)( state_->handleInput(*this , input); ) aktualizácia virtuálnej neplatné ()( state_->update(*this); ) // Iné metódy... private : HeroineState* state_; );

Ak chcete "zmeniť stav", stačí, aby state_ ukazoval na iný objekt HeroineState. Z toho vlastne pozostáva vzor štátu.

Vyzerá celkom podobne ako šablóny Strategy a Type Object od GoF. Vo všetkých troch máme hlavný objekt delegovaný na otroka. Rozdiel je v tom účel.

  • Účelom stratégie je klesajúca konektivita(oddeliť) medzi hlavnou triedou a jej správaním.
  • Účelom objektu typu je vytvoriť množstvo objektov, ktoré sa správajú rovnako, zdieľaním objektu spoločného typu medzi sebou.
  • Účelom štátu je zmeniť správanie hlavného objektu zmenou objektu, na ktorý deleguje.

Kde sú tieto štátne objekty?

Je tu niečo, čo som ti nepovedal. Ak chcete zmeniť stav, musíme priradiť state_ novú hodnotu ukazujúcu na nový stav, ale odkiaľ tento objekt pochádza? V našom príklade enum nie je o čom premýšľať: hodnoty enum sú len primitívy ako čísla. Ale teraz sú naše štáty reprezentované triedami, čo znamená, že potrebujeme ukazovatele na skutočné prípady. Existujú dve najčastejšie odpovede:

Statické stavy

Ak objekt stavu nemá žiadne ďalšie polia, jediná vec, ktorú ukladá, je ukazovateľ na internú virtuálnu tabuľku metód, aby bolo možné tieto metódy volať. V tomto prípade nie je potrebné mať viac ako jednu inštanciu triedy: každá inštancia bude stále rovnaká.

Ak váš štát nemá žiadne polia a iba jednu virtuálnu metódu, môžete vzor ešte viac zjednodušiť. Každý vymeníme Triedaštát funkciu stav – bežná funkcia najvyššej úrovne. A podľa toho aj pole štát_ v našej hlavnej triede sa zmení na jednoduchý funkčný ukazovateľ.

Je celkom možné vystačiť si len s jedným statické kopírovať. Aj keď máte veľa FSM v rovnakom stave súčasne, všetky môžu ukazovať na rovnakú statickú inštanciu, pretože v nej nie je nič špecifické pre stavový stroj.

Kam umiestnite statickú inštanciu je len na vás. Nájdite miesto, kde to bude vhodné. Dajme našu inštanciu do základnej triedy. Bez dôvodu.

class HeroineState ( public : statický StandingState stojaci; statický DuckingState zohýbanie sa; statické JumpingState skákanie; statické DivingState potápanie; // Zvyšok kódu... };

Každé z týchto statických polí je inštanciou stavu používaného hrou. Aby hrdinka skočila, stojaci stav urobí niečo ako:

if (vstup == PRESS_B) ( heroine.state_ = &HeroineState::skákanie; heroine.setGraphics(IMAGE_JUMP); )

Štátne inštancie

Niekedy predchádzajúca možnosť nezaberie. Statický stav nie je vhodný pre skrčený stav. Má pole chargeTime_ a je špecifické pre hrdinku, ktorá sa bude prikrčiť. V našom prípade to bude fungovať ešte lepšie, pretože máme len jednu hrdinku, no ak by sme chceli pridať kooperáciu pre dvoch hráčov, budeme mať veľké problémy.

V tomto prípade by sme mali vytvoriť objekt stavu, keď sa doň presunieme. To umožní každému FSM mať svoju vlastnú štátnu inštanciu. Samozrejme, ak pridelíme pamäť pre Nový stavu, to znamená, že by sme mali uvoľniť obsadená pamäť aktuálnej. Musíme byť opatrní, pretože kód, ktorý spôsobuje zmeny, je v aktuálnom stave metódy. Nechceme to odstrániť zospodu.

Namiesto toho necháme handleInput() na HeroineState voliteľne vrátiť nový stav. Keď sa to stane, Heroine odstráni starý stav a nahradí ho novým, takto:

void Heroine::handleInput(Input input) ( HeroineState* state = state_->handleInput(*this , input); if (state != NULL ) ( delete state_; state_ = state; ) )

Týmto spôsobom neodstránime predchádzajúci stav, kým sa nevrátime z našej metódy. Teraz môže stojaci stav prejsť do stavu potápania vytvorením novej inštancie:

HeroineState* StandingState::handleInput(Heroine& heroine, Input input) ( if (input == PRESS_DOWN) ( // Iný kód... return new DuckingState(); ) // Zostaňte v tomto stave. return NULL ; )

Keď môžem, radšej používam statické stavy, pretože nezaberajú pamäť a cykly CPU prideľovaním objektov pri každej zmene stavu. Pre podmienky, ktoré nie sú viac ako len štát- to je presne to, čo potrebujete.

Samozrejme, keď dynamicky prideľujete pamäť pre stav, mali by ste myslieť na možnú fragmentáciu pamäte. Pomôcť môže šablóna Object Pool.

Kroky prihlásenia a odhlásenia

Vzor Stav je navrhnutý tak, aby zahŕňal všetko správanie a súvisiace údaje v rámci jednej triedy. Ide nám to celkom dobre, no stále sú tu nejaké nejasné detaily.

Keď hrdinka zmení stav, prepneme aj jej sprita. Práve teraz tento kód patrí štátu, s koho ona sa prepne. Keď stav prejde z potápania do stoja, ponor vytvorí svoj obraz:

HeroineState* DuckingState::handleInput(Heroine& heroine, Input input) ( if (input == RELEASE_DOWN) ( heroine.setGraphics(IMAGE_STAND); return new StandingState(); ) // Iný kód... )

Naozaj chceme, aby každý štát ovládal svoju vlastnú grafiku. Môžeme to dosiahnuť pridaním do stavu vstupná akcia (vstupná akcia):

class StandingState: public HeroineState ( public : virtuálny vstup do prázdnoty (hrdinka a hrdinka)( heroine.setGraphics(IMAGE_STAND); ) // Iný kód... );

Vráťme sa k Heroine, upravíme kód, aby sme zabezpečili, že zmenu stavu bude sprevádzať volanie funkcie vstupnej akcie nového stavu:

void Heroine::handleInput(Input input) ( HeroineState* state = state_->handleInput(*this , input); if (state != NULL ) ( delete state_; state_ = state; // Zavolajte vstupnú akciu nového stavu. stav_->vstup(*toto ); ))

Tým sa zjednoduší kód DuckingState:

HeroineState* DuckingState::handleInput(Heroine& heroine, Input input) ( if (input == RELEASE_DOWN) ( return new StandingState(); ) // Iný kód... )

Toto všetko robí iba prepnutie do stojaceho stavu a o grafiku sa stará stojaci stav. Teraz sú naše štáty skutočne zapuzdrené. Ďalšou príjemnou vlastnosťou takejto vstupnej akcie je, že sa spúšťa pri vstupe do stavu bez ohľadu na stav, v ktorom sa nachádza ktoré boli sme tam.

Väčšina grafov skutočného stavu má viacero prechodov do rovnakého stavu. Naša hrdinka môže napríklad strieľať zo zbrane v stoji, v sede alebo pri skákaní. To znamená, že všade, kde sa to stane, môžeme mať duplicitu kódu. Vstupná akcia vám umožňuje zhromaždiť ho na jednom mieste.

Môžete to urobiť analogicky výstupná akcia (výstupná akcia). Toto bude jednoducho metóda, ktorú predtým zavoláme na štát opúšťať a prepnite sa do nového stavu.

A čo sme dosiahli?

Strávil som toľko času predajom FSM a teraz sa chystám spod teba vytiahnuť koberec. Všetko, čo som doteraz povedal, je pravda a je skvelým riešením problémov. Stáva sa však, že najdôležitejšie výhody konečných automatov sú zároveň ich najväčšími nevýhodami.

Stavový automat vám pomôže vážne rozmotať váš kód tým, že ho usporiada do veľmi prísnej štruktúry. Všetko, čo máme, je pevná množina stavov, jeden aktuálny stav a pevne zakódované prechody.

Konečný automat nie je Turingov dokončený. Teória automatov opisuje úplnosť prostredníctvom série abstraktných modelov, z ktorých každý je zložitejší ako predchádzajúci. Turingov stroj je jedným z najvýraznejších.

„Turing kompletný“ znamená systém (zvyčajne programovací jazyk), ktorý je dostatočne expresívny na implementáciu Turingovho stroja. To zase znamená, že všetky Turingove kompletné jazyky sú približne rovnako výrazné. FSM nie sú dostatočne výrazné na vstup do tohto klubu.

Ak sa pokúsite použiť stavový automat na niečo zložitejšie, ako je herná AI, okamžite narazíte na obmedzenia tohto modelu. Našťastie sa naši predchodcovia naučili obchádzať niektoré prekážky. Túto kapitolu ukončím niekoľkými takýmito príkladmi.

Konkurenčný štátny automat

Rozhodli sme sa pridať našej hrdinke schopnosť nosiť zbraň. Hoci je teraz ozbrojená, stále môže robiť všetko, čo mohla robiť predtým: behať, skákať, prikrčiť sa atď. Ale teraz, keď robí toto všetko, môže strieľať aj zo zbrane.

Ak chceme toto správanie vtesnať do rámca FSM, budeme musieť zdvojnásobiť počet stavov. Pre každý zo štátov budeme musieť vytvoriť ďalší rovnaký, ale pre hrdinku so zbraňou: v stoji, v stoji so zbraňou, skákať, skákať so zbraňou... Dobre, chápete.

Ak pridáte niekoľko ďalších zbraní, počet štátov sa kombinačne zvýši. A to nie je len hromada štátov, ale aj hromada opakovaní: ozbrojené a neozbrojené štáty sú takmer totožné s výnimkou časti kódu zodpovednej za streľbu.

Tu je problém, že si mýlime dve časti štátu – že to robí No a čo drží v rukách- v jednom stroji. Aby sme mohli modelovať všetky možné kombinácie, musíme pre každú vytvoriť stav páry. Riešenie je zrejmé: musíte vytvoriť dva samostatné štátne automaty.

Ak sa chceme zjednotiť n akčné stavy a m stavov toho, čo držíme v rukách do jedného konečného automatu – potrebujeme n x mštátov. Ak máme dva guľomety, budeme potrebovať n+mštátov.

Náš prvý stavový automat ponecháme bez zmien. A okrem nej vytvoríme ďalší stroj na opis toho, čo hrdinka drží. Teraz bude mať Heroine dve „štátne“ referencie, jednu pre každý stroj.

triedna hrdinka ( // Zvyšok kódu... private : HeroineState* state_; HeroineState* vybavenie_; );

Pre ilustráciu používame úplnú implementáciu vzoru State pre druhý stavový automat, aj keď v praxi by v tomto prípade stačil jednoduchý booleovský príznak.

Keď hrdinka deleguje vstup stavom, odovzdá preklad obom stavovým automatom:

void Heroine::handleInput(Input input) ( state_->handleInput(*toto , vstup); equipment_->handleInput(*toto , vstup); )

Zložitejšie systémy môžu obsahovať automaty s konečným stavom, ktoré môžu absorbovať časť vstupu, takže iné stroje ho už nedostanú. To nám umožní predísť situácii, keď niekoľko strojov reaguje na rovnaký vstup.

Každý stavový automat môže reagovať na vstup, vytvárať správanie a meniť svoj stav nezávisle od ostatných stavových automatov. A keď oba stavy prakticky nesúvisia, funguje to skvele.

V praxi sa môžete stretnúť so situáciou, kedy sa štáty navzájom ovplyvňujú. Nemôže napríklad strieľať pri skákaní alebo napríklad vykonávať sklzový útok, keď je ozbrojená. Aby ste zabezpečili toto správanie a koordináciu automatov v kóde, budete sa musieť vrátiť k rovnakej kontrole hrubou silou cez if ďalší konečný automat. Nie je to najelegantnejšie riešenie, ale aspoň funguje.

Hierarchický štátny stroj

Po ďalšej revitalizácii hrdinkinho správania bude mať pravdepodobne celý rad podobných stavov. Napríklad státie, chôdza, beh a šmýkanie sa dolu svahmi nemôže nastať. V ktoromkoľvek z týchto stavov stlačením B skočí a stlačením dole sa prikrčí.

V najjednoduchšej implementácii stavového automatu sme tento kód duplikovali pre všetky stavy. Ale samozrejme by bolo oveľa lepšie, keby sme kód museli napísať iba raz a potom by sme ho mohli znova použiť pre všetky štáty.

Ak by to bol len objektovo orientovaný kód a nie stavový stroj, mohli by sme použiť techniku ​​na oddelenie kódu medzi stavmi nazývanú dedičnosť. Môžete definovať triedu pre základný stav, ktorá zvládne skákanie a prikrčenie. Stánie, chôdza, beh a váľanie sa za to dedia a pridávajú k tomu svoje vlastné ďalšie správanie.

Toto rozhodnutie má dobré aj zlé následky. Dedičnosť je výkonný nástroj na opätovné použitie kódu, no zároveň poskytuje veľmi silnú súdržnosť medzi dvoma časťami kódu. Kladivo je príliš ťažké na to, aby sa dalo bezhlavo udrieť.

V tejto podobe bude výsledná štruktúra tzv hierarchický stavový stroj(alebo hierarchický automat). A každá podmienka môže mať svoju vlastnú superštát(samotný štát sa nazýva subštátu). Keď nastane udalosť a podštát ju nespracuje, prejde do reťazca superstavov. Inými slovami, vyzerá to ako prepísanie zdedenej metódy.

V skutočnosti, ak na implementáciu FSM použijeme pôvodný vzor State, na implementáciu hierarchie už môžeme použiť dedičnosť tried. Definujme základnú triedu pre nadtriedu:

trieda OnGroundState: public HeroineState ( public : virtuálna void handleInput (Hrdinka a hrdinka, vstup)( if (vstup == PRESS_B) ( // Skok... ) else if (vstup == PRESS_DOWN) ( // Drep... ) ) );

A teraz to zdedí každá podtrieda:

class DuckingState: public OnGroundState ( public : virtuálna void handleInput (Hrdinka a hrdinka, vstup)( if (vstup == RELEASE_DOWN) ( // Vstaň... ) else ( // Vstup nie je spracovaný. Preto ho míňame vyššie v hierarchii. OnGroundState::handleInput(hrdinka, vstup); )));

Samozrejme, toto nie je jediný spôsob implementácie hierarchie. Ale ak nepoužijete šablónu Gang of Four State, nebude to fungovať. Namiesto toho môžete modelovať jasnú hierarchiu súčasných štátov a superštátov stoh stavov namiesto jedného stavu v hlavnej triede.

Aktuálny stav bude v hornej časti zásobníka, pod ním je jeho superstav, potom superstav pre toto superštáty atď. A keď potrebujete implementovať správanie špecifické pre stav, začnete od vrcholu zásobníka a postupujete smerom nadol, kým to daný stav nespracuje. (A ak to nespracuje, potom to jednoducho ignorujete).

Automat s pamäťou zásobníka

Existuje ďalšie bežné rozšírenie stavových automatov, ktoré tiež používa zásobník stavov. Len tu zásobník predstavuje úplne iný koncept a používa sa na riešenie iných problémov.

Problém je v tom, že štátny automat nemá koncepciu príbehov. Vieš v akom si stave? si, ale nemáte informácie o tom, v akom stave sa nachádzate boli. A preto neexistuje jednoduchý spôsob, ako sa vrátiť do predchádzajúceho stavu.

Tu je jednoduchý príklad: Predtým sme našej nebojácnej hrdinke dovolili, aby sa vyzbrojila po zuby. Keď vystrelí zo zbrane, potrebujeme nový stav, aby sme prehrali animáciu výstrelu, splodili guľku a sprievodné vizuálne efekty. Aby sme to urobili, vytvoríme nový FiringState a urobíme doň prechody zo všetkých stavov, v ktorých môže hrdinka strieľať stlačením tlačidla streľby.

Keďže toto správanie je duplikované medzi viacerými stavmi, tu je možné použiť hierarchický stavový automat na opätovné použitie kódu.

Problém je v tom, že musíte nejako pochopiť, do akého stavu musíte ísť. po Streľba. Hrdinka môže odpáliť celý klip, zatiaľ čo stojí, behá, skáče alebo sa krčí. Po dokončení sekvencie streľby sa musí vrátiť do stavu, v akom bola pred streľbou.

Ak sa pripútame k čistému FSM, okamžite zabudneme, v akom stave sme boli. Aby sme to mali prehľad, musíme definovať veľa takmer identických stavov – streľba v stoji, streľba v behu, streľba zo skoku atď. Máme teda pevne zakódované prechody, ktoré po dokončení prejdú do správneho stavu.

To, čo skutočne potrebujeme, je možnosť uložiť si stav, v ktorom sme boli pred streľbou, a po streľbe si ho znova zapamätať. Tu nám opäť môže pomôcť teória automatov. Zodpovedajúca dátová štruktúra sa nazýva Pushdown Automaton.

Kde v konečnom automate máme jediný ukazovateľ na stav, v automate s pamäťou je ich stoh. V MFŠ prechod do nového stavu nahrádza predchádzajúci. Stroj s pamäťou zásobníka vám to tiež umožňuje, ale pridáva ďalšie dve operácie:

    Môžeš miesto (TAM) nový stav do zásobníka. Aktuálny stav bude vždy na vrchole zásobníka, takže ide o operáciu prechodu do nového stavu. Zároveň však starý stav zostáva priamo pod súčasným stavom zásobníka a nezmizne bez stopy.

    Môžeš extrakt (pop) najvyšší stav zo zásobníka. Štát zaniká a to, čo bolo pod ním, sa stáva aktuálnym.

To je všetko, čo k streľbe potrebujeme. Tvoríme jediná vec strelecký stav. Keď stlačíme tlačidlo streľby v inom stave, my miesto (TAM) stav snímania zásobníka. Keď skončí animácia streľby, my extrakt (pop) a stroj s pamäťou zásobníka nás automaticky vráti do predchádzajúceho stavu.

Nakoľko sú skutočne užitočné?

Aj pri tomto rozšírení štátnych automatov sú ich možnosti stále dosť obmedzené. V AI dnes prevláda trend používať veci ako stromy správania(stromy správania) a plánovacie systémy(plánovacie systémy). A ak sa obzvlášť zaujímate o oblasť AI, celá táto kapitola by vám mala nahnať chuť do jedla. Aby ste ho uspokojili, budete sa musieť obrátiť na iné knihy.

To vôbec neznamená, že konečné automaty, automaty s pamäťou zásobníka a iné podobné systémy sú úplne zbytočné. Pre niektoré veci sú to dobré modelovacie nástroje. Stavové automaty sú užitočné, keď:

  • Máte entitu, ktorej správanie sa mení v závislosti od jej vnútorného stavu.
  • Táto podmienka je striktne rozdelená na relatívne malý počet konkrétnych možností.
  • Entita neustále reaguje na sériu vstupných príkazov alebo udalostí.

V hrách sa stavové automaty zvyčajne používajú na modelovanie AI, ale môžu sa použiť aj na implementáciu používateľského vstupu, navigácie v ponuke, analýzy textu, sieťových protokolov a iného asynchrónneho správania.

"VzorŠtát" source.ru

Stav je vzor správania sa objektu, ktorý špecifikuje rôzne funkcie v závislosti od vnútorného stavu objektu. pôvodný zdroj webovej stránky

Podmienky, úloha, účel

Umožňuje objektu meniť svoje správanie v závislosti od jeho vnútorného stavu. Keďže správanie sa môže meniť úplne ľubovoľne bez akýchkoľvek obmedzení, zvonku sa zdá, že sa zmenila trieda objektu.

Motivácia

Zvážte triedu TCPConnection, čo predstavuje sieťové pripojenie. Objekt tejto triedy môže byť v jednom z niekoľkých stavov: Založené(nainštalované), Počúvanie(počúvanie), ZATVORENÉ(ZATVORENÉ). Keď objekt TCPConnection prijíma požiadavky od iných objektov, reaguje rôzne v závislosti od aktuálneho stavu. Napríklad odpoveď na požiadavku OTVORENÉ(otvorené) závisí od toho, či je spojenie v stave ZATVORENÉ alebo Založené. Vzor stavu popisuje, ako objekt TCPConnection sa môže správať odlišne v rôznych stavoch. zdroj lokality pôvodná lokalita

Hlavnou myšlienkou tohto vzoru je predstaviť abstraktnú triedu TCPState reprezentovať rôzne stavy pripojenia. Táto trieda deklaruje rozhranie, ktoré je spoločné pre všetky triedy, ktoré popisujú rôznych pracovníkov. pôvodný zdroj.ru

stave. V týchto podtriedach TCPState implementuje sa správanie špecifické pre daný stav. Napríklad v triedach TCPEstablished A TCPClosed implementované správanie špecifické pre daný stav Založené A ZATVORENÉ resp. pôvodný zdroj webovej stránky

original.ru

Trieda TCPConnection ukladá objekt stavu (inštanciu podtriedy TCPState), ktorý predstavuje aktuálny stav pripojenia a deleguje všetky požiadavky závislé od stavu na tento objekt. TCPConnection používa vlastnú inštanciu podtriedy TCPState celkom jednoduché: volanie metód jedného rozhrania TCPState, len v závislosti od toho, aká konkrétna podtrieda je aktuálne uložená TCPState-a - výsledok je iný, t.j. v skutočnosti sa vykonávajú operácie, ktoré sú špecifické len pre tento stav pripojenia. zdroj original.ru

A zakaždým, keď sa zmení stav pripojeniaTCPConnection zmení svoj stavový objekt. Napríklad, keď sa nadviazané spojenie uzavrie, TCPConnection nahrádza inštanciu triedy TCPEstablished kopírovať TCPClosed. pôvodný zdroj stránky

Znaky aplikácie, použitie vzoru State

Vzor stavu použite v nasledujúcich prípadoch: source.ru
  1. Keď správanie objektu závisí od jeho stavu a musí sa zmeniť za behu. zdroj original.ru
  2. Keď operačný kód obsahuje podmienené príkazy pozostávajúce z mnohých vetiev, v ktorých výber vetvy závisí od stavu. Typicky je v tomto prípade stav reprezentovaný vymenovanými konštantami. Rovnaká štruktúra podmieneného príkazu sa často opakuje v niekoľkých operáciách. Vzor stavu navrhuje umiestniť každú vetvu do samostatnej triedy. To vám umožňuje považovať stav objektu za nezávislý objekt, ktorý sa môže meniť nezávisle od ostatných. zdroj lokality pôvodná lokalita

Riešenie

pôvodný zdroj webovej stránky

source.ru

Účastníci štátneho vzoru

source.ru
  1. Kontext(TCPConnection) - kontext.
    Definuje jediné rozhranie pre klientov.
    Ukladá inštanciu podtriedy ConcreteState, ktorý určuje aktuálny stav. kódové laboratórium.
  2. Štát(TCPSate) - štát.
    Definuje rozhranie na zapuzdrenie správania spojeného s konkrétnym stavom kontextu. zdroj stránky pôvodný
  3. Podtriedy ConcreteState(TCPEstablished, TCPListen, TCPClosed) - špecifický stav.
    Každá podtrieda implementuje správanie spojené s nejakým kontextovým stavom Kontext. pôvodný zdroj stránky

Schéma na použitie vzoru Stav

Trieda Kontext deleguje požiadavky na aktuálny objekt ConcreteState. pôvodný zdroj stránky

Kontext sa môže odovzdať ako argument objektu Štát, ktorá žiadosť spracuje. To umožňuje objektu stavu ( ConcreteState) v prípade potreby vstúpte do kontextu. kódové laboratórium.

Kontext- Toto je hlavné rozhranie pre klientov. Klienti môžu konfigurovať kontext pomocou stavových objektov Štát(presnejšie ConcreteState). Akonáhle je kontext nakonfigurovaný, klienti už nemusia komunikovať priamo so stavovými objektmi (iba cez spoločné rozhranie Štát). source.ru

V tomto prípade tiež Kontext alebo samotné podtriedy ConcreteState môže rozhodnúť, za akých podmienok a v akom poradí k zmene stavov dôjde. pôvodný zdroj webovej stránky

Otázky týkajúce sa implementácie štátneho vzoru

Otázky týkajúce sa implementácie štátneho vzoru: zdroj original.ru
  1. Čo určuje prechody medzi stavmi.
    Štátny vzorec nehovorí nič o tom, ktorý účastník určuje podmienky (kritériá) prechodu medzi štátmi. Ak sú kritériá pevne stanovené, môžu sa implementovať priamo v triede Kontext. Vo všeobecnosti je však flexibilnejší a správnejší prístup povoliť podtriedy samotnej triedy Štát určiť ďalší stav a moment prechodu. Aby ste to urobili v triede Kontext musíme pridať rozhranie, ktoré umožňuje z objektov Štát nastaviť jeho stav.
    Táto decentralizovaná prechodová logika sa ľahšie upravuje a rozširuje – stačí definovať nové podtriedy Štát. Nevýhodou decentralizácie je, že každá podtrieda Štát musí „vedieť“ aspoň o jednej podtriede iného stavu (do ktorého môže skutočne prepnúť aktuálny stav), čo zavádza implementačné závislosti medzi podtriedami. zdroj stránky pôvodný

    zdroj original.ru
  2. Tabuľková alternatíva.
    Existuje ďalší spôsob, ako štruktúrovať štátom riadený kód. Toto je princíp konečného automatu. Používa tabuľku na mapovanie vstupov na prechody stavov. S jeho pomocou môžete určiť, do ktorého stavu musíte prejsť, keď prídu určité vstupné údaje. V podstate nahrádzame podmienený kód vyhľadávaním v tabuľke.
    Hlavnou výhodou stroja je jeho pravidelnosť: na zmenu kritérií prechodu stačí upraviť iba údaje, nie kód. Ale sú tu aj nevýhody:
    - vyhľadávanie v tabuľke je často menej efektívne ako volanie funkcie,
    - prezentácia logiky prechodu v jednotnom tabuľkovom formáte robí kritériá menej explicitnými, a preto sú ťažšie zrozumiteľné,
    - zvyčajne je ťažké pridať akcie, ktoré sprevádzajú prechody medzi stavmi. Tabuľková metóda zohľadňuje stavy a prechody medzi nimi, je však potrebné ju doplniť, aby bolo možné vykonávať ľubovoľné výpočty pri každej zmene stavu.
    Hlavný rozdiel medzi stavovými automatmi založenými na tabuľke a stavom vzoru možno formulovať takto: Stav vzoru modeluje správanie závislé od stavu a tabuľková metóda sa zameriava na definovanie prechodov medzi stavmi. original.ru

    pôvodný zdroj stránky
  3. Vytváranie a ničenie štátnych objektov.
    Počas procesu vývoja si zvyčajne musíte vybrať medzi:
    - vytváranie stavových objektov, keď sú potrebné, a ich zničenie ihneď po použití,
    - ich vytváranie vopred a navždy.

    Prvá možnosť je vhodnejšia, keď nie je vopred známe, do akých stavov systém spadne, a kontext mení stav pomerne zriedka. Zároveň nevytvárame objekty, ktoré sa nikdy nepoužijú, čo je dôležité, ak je v stavových objektoch uložených veľa informácií. Keď sa zmeny stavu vyskytujú často, takže nechcete zničiť objekty, ktoré ich reprezentujú (pretože môžu byť čoskoro opäť potrebné), mali by ste použiť druhý prístup. Čas na vytváranie predmetov sa strávi iba raz, na samom začiatku, a čas na ničenie sa nestrávi vôbec. Je pravda, že tento prístup sa môže ukázať ako nepohodlný, pretože kontext musí uchovávať odkazy na všetky stavy, do ktorých by sa systém mohol teoreticky dostať. zdroj original.ru

    Pôvodný zdroj.ru
  4. Použitie dynamických zmien.
    Je možné zmeniť správanie na požiadanie zmenou triedy objektu za behu, ale väčšina objektovo orientovaných jazykov to nepodporuje. Výnimkou je Perl, JavaScript a ďalšie jazyky založené na skriptovacích strojoch, ktoré poskytujú takýto mechanizmus, a preto podporujú stav vzoru priamo. To umožňuje objektom meniť svoje správanie zmenou kódu triedy. source.ru

    Pôvodný zdroj .ru

výsledky

Výsledky používania stav vzoru: zdroj original.ru
  1. Lokalizuje správanie závislé od stavu.
    A rozdeľuje ho na časti zodpovedajúce stavom. Vzor stavu dáva všetko správanie spojené s konkrétnym stavom do samostatného objektu. Pretože kód závislý od stavu je úplne obsiahnutý v jednej z podtried triedy Štát, potom môžete pridať nové stavy a prechody jednoducho vytvorením nových podtried.
    Namiesto toho by sa dali použiť dátové členy na definovanie vnútorných stavov a potom operácií objektu Kontext skontroluje tieto údaje. Ale v tomto prípade by podobné podmienené príkazy alebo príkazy vetvy boli rozptýlené po celom kóde triedy Kontext. Pridanie nového stavu by si však vyžiadalo zmenu viacerých operácií, čo by sťažilo údržbu. Stavový vzor rieši tento problém, ale vytvára aj ďalší, pretože správanie pre rôzne stavy je nakoniec rozdelené medzi niekoľko podtried Štát. Tým sa zvyšuje počet tried. Samozrejme, jedna trieda je kompaktnejšia, ale ak existuje veľa stavov, potom je takáto distribúcia efektívnejšia, pretože inak by sa museli zaoberať ťažkopádnymi podmienenými príkazmi.
    Ťažkopádne podmienené vyhlásenia sú nežiaduce, rovnako ako dlhé postupy. Sú príliš monolitické, a preto sa úprava a rozšírenie kódu stáva problémom. Vzor stavu ponúka lepší spôsob štruktúrovania kódu závislého od stavu. Logika popisujúca prechody stavov už nie je zabalená do monolitických príkazov ak alebo prepínač, ale distribuované medzi podtriedy Štát. Zapuzdrením každého prechodu a akcie do triedy sa štát stáva plnohodnotným objektom. To zlepšuje štruktúru kódu a sprehľadňuje jeho účel. original.ru
  2. Zvýrazňuje prechody medzi stavmi.
    Ak objekt definuje svoj aktuálny stav výlučne z hľadiska interných údajov, potom prechody medzi stavmi nemajú žiadnu explicitnú reprezentáciu; objavujú sa len ako priradenia k určitým premenným. Zavedením samostatných objektov pre rôzne stavy sú prechody explicitnejšie. Okrem toho predmety Štát môže chrániť kontext Kontext z nesúladu vnútorných premenných, keďže prechody z hľadiska kontextu sú atomické akcie. Ak chcete vykonať prechod, musíte zmeniť hodnotu iba jednej premennej (objektovej premennej Štát v triede Kontext), a nie niekoľko. source.ru
  3. Stavové objekty je možné zdieľať.
    Ak je v stave objektu Štát neexistujú žiadne premenné inštancie, čo znamená, že stav, ktorý predstavuje, je kódovaný výlučne samotným typom, potom môžu rôzne kontexty zdieľať rovnaký objekt Štát. Keď sú štáty oddelené týmto spôsobom, sú to v podstate oportunisti (pozri oportunistický vzorec), ktorí nemajú žiadny vnútorný stav, iba správanie. zdroj lokality pôvodná lokalita

Príklad

Pozrime sa na implementáciu príkladu z časti "", t.j. vytvorenie nejakej jednoduchej architektúry pripojenia TCP. Toto je zjednodušená verzia TCP protokolu, samozrejme nereprezentuje celý protokol a dokonca ani všetky stavy TCP spojení. Pôvodný zdroj.ru

Najprv si definujme triedu TCPConnection, ktorý poskytuje rozhranie na prenos dát a spracováva požiadavky na zmenu stavu: TCPConnection. Pôvodný zdroj.ru

V členskej premennej štát trieda TCPConnection je uložená inštancia triedy TCPState. Táto trieda duplikuje rozhranie zmeny stavu definované v triede TCPConnection. zdroj original.ru

pôvodný zdroj stránky

TCPConnection deleguje všetky požiadavky závislé od stavu na inštanciu uloženú v stave TCPState. Aj v triede TCPConnection existuje operácia ChangeState, pomocou ktorého môžete do tejto premennej zapísať ukazovateľ na iný objekt TCPState. Konštruktor triedy TCPConnection inicializuje štát ukazovateľ do uzavretého stavu TCPClosed(definujeme to nižšie). zdroj original.ru

Pôvodný zdroj.ru

Každá operácia TCPState akceptuje inštanciu TCPConnection ako parameter, čím sa objekt povolí TCPState prístup k údajom objektu TCPConnection a zmeniť stav pripojenia. .ru

V triede TCPState implementoval predvolené správanie pre všetky požiadavky, ktoré mu boli delegované. Môže tiež zmeniť stav objektu TCPConnection cez operáciu ChangeState. TCPState nachádza v rovnakom balení ako TCPConnection, takže má tiež prístup k tejto operácii: TCPState . pôvodný zdroj stránky

original.ru

V podtriedach TCPState implementované správanie závislé od stavu. TCP spojenie môže byť v mnohých stavoch: Založené(nainštalované), Počúvanie(počúvanie), ZATVORENÉ(uzavreté) atď. a každý z nich má svoju vlastnú podtriedu TCPState. Pre jednoduchosť podrobne zvážime iba 3 podtriedy - TCPEstablished, TCPListen A TCPClosed. pôvodný zdroj webovej stránky

Zdroj Ru

V podtriedach TCPState implementuje správanie závislé od stavu pre tie požiadavky, ktoré sú v danom stave platné. .ru

pôvodný zdroj stránky

Po vykonaní akcií špecifických pre daný stav tieto operácie zdroj original.ru

spôsobiť ChangeState zmeniť stav objektu TCPConnection. On sám nemá informácie o protokole TCP. Ide o podtriedy TCPState definovať prechody medzi stavmi a akciami diktovanými protokolom. original.ru

zdroj original.ru

Známe aplikácie štátneho vzoru

Ralph Johnson a Jonathan Zweig charakterizujú stavový vzor a popisujú ho vo vzťahu k protokolu TCP.
Najpopulárnejšie programy interaktívneho kreslenia poskytujú „nástroje“ na vykonávanie priamych manipulačných operácií. Napríklad nástroj na kreslenie čiar umožňuje používateľovi kliknúť myšou na ľubovoľný bod a potom pohybom myši nakresliť čiaru z tohto bodu. Nástroj na výber vám umožňuje vybrať niektoré tvary. Zvyčajne sú všetky dostupné nástroje umiestnené v palete. Úlohou používateľa je vybrať a použiť nástroj, ale v skutočnosti sa správanie editora mení podľa toho, ako sa nástroj mení: pomocou nástroja na kreslenie vytvárame tvary, pomocou nástroja na výber ich vyberáme atď. pôvodný zdroj webovej stránky

Na vyjadrenie závislosti správania editora od aktuálneho nástroja môžete použiť vzor stavu. pôvodný zdroj.ru

Môžete definovať abstraktnú triedu Nástroj, ktorého podtriedy implementujú správanie špecifické pre nástroj. V grafickom editore je uložený odkaz na aktuálny objekt Tiežl a deleguje mu prichádzajúce požiadavky. Keď vyberiete nástroj, editor použije iný objekt, čo spôsobí zmenu správania. .ru

Táto technika sa používa v rámci grafických editorov HotDraw a Unidraw. Umožňuje zákazníkom jednoducho definovať nové typy nástrojov. IN HotDraw Trieda DrawingController preposiela požiadavky na aktuálny objekt Nástroj. IN Unidraw zodpovedajúce triedy sa nazývajú Divák A Nástroj. Nižšie uvedený diagram tried poskytuje schematické znázornenie rozhraní tried Nástroj

original.ru

Účel štátneho vzoru

  • Vzor Stav umožňuje objektu zmeniť svoje správanie v závislosti od jeho vnútorného stavu. Zdá sa, že objekt zmenil svoju triedu.
  • Vzor Stav je objektovo orientovaná implementácia stavového automatu.

Problém, ktorý treba vyriešiť

Správanie objektu závisí od jeho stavu a musí sa meniť počas vykonávania programu. Takáto schéma môže byť implementovaná pomocou mnohých podmienených operátorov: na základe analýzy súčasného stavu objektu sa vykonajú určité akcie. Pri veľkom počte stavov však budú podmienené príkazy roztrúsené po celom kóde a takýto program bude náročný na údržbu.

Diskusia o štátnom vzore

Vzor štátu rieši tento problém takto:

  • Zavádza triedu Context, ktorá definuje rozhranie k vonkajšiemu svetu.
  • Predstavuje abstraktnú triedu State.
  • Predstavuje rôzne "stavy" stavového automatu ako podtriedy stavu.
  • Trieda Context má ukazovateľ na aktuálny stav, ktorý sa zmení, keď sa zmení stav stavového automatu.

Vzor stavu nedefinuje, kde presne je určená podmienka prechodu do nového stavu. Existujú dve možnosti: trieda Context alebo podtriedy State. Výhodou poslednej možnosti je jednoduché pridávanie nových odvodených tried. Nevýhodou je, že každá podtrieda štátu musí vedieť o svojich susedoch, aby mohla prejsť do nového stavu, čo zavádza závislosti medzi podtriedami.

Existuje aj alternatívny prístup založený na tabuľkách k navrhovaniu konečných automatov, založený na použití tabuľky, ktorá jedinečne mapuje vstupné dáta na prechody medzi stavmi. Tento prístup má však nevýhody: je ťažké pridať vykonávanie akcií pri vykonávaní prechodov. Prístup podľa vzoru stavu používa kód (namiesto dátových štruktúr) na prechody medzi stavmi, takže tieto akcie sa dajú ľahko pridať.

Štruktúra štátneho vzoru

Trieda Context definuje externé rozhranie pre klientov a ukladá odkaz na aktuálny stav objektu State. Rozhranie abstraktnej základnej triedy State je rovnaké ako rozhranie Context s výnimkou jedného dodatočného parametra - ukazovateľa na inštanciu Context. Triedy odvodené od stavu definujú správanie špecifické pre daný stav. Trieda Context wrapper deleguje všetky prijaté požiadavky na objekt "aktuálny stav", ktorý môže použiť prijatý dodatočný parameter na prístup k inštancii Context.

Vzor Stav umožňuje objektu zmeniť svoje správanie v závislosti od jeho vnútorného stavu. Podobný obraz možno pozorovať aj pri prevádzke predajného automatu. Stroje môžu mať rôzne stavy v závislosti od dostupnosti tovaru, množstva prijatých mincí, schopnosti zamieňať peniaze atď. Po vybratí a zaplatení produktu kupujúcim sú možné nasledujúce situácie (stavy):

  • Tovar odovzdajte kupujúcemu, zmena nie je potrebná.
  • Dajte kupujúcemu tovar a zmeňte ho.
  • Kupujúci nedostane tovar z dôvodu nedostatku peňazí.
  • Kupujúci neprevezme tovar z dôvodu jeho neprítomnosti.

Použitie štátneho vzoru

  • Definujte existujúcu alebo vytvorte novú triedu Context wrapper, ktorú bude klient používať ako „stavový stroj“.
  • Vytvorte základnú triedu State, ktorá replikuje rozhranie triedy Context. Každá metóda má jeden dodatočný parameter: inštanciu triedy Context. Trieda State môže definovať akékoľvek užitočné „predvolené“ správanie.
  • Vytvorte triedy odvodené od štátu pre všetky možné stavy.
  • Trieda Context wrapper má odkaz na objekt aktuálneho stavu.
  • Trieda Context jednoducho deleguje všetky požiadavky prijaté od klienta na objekt „aktuálny stav“, pričom adresa objektu Context sa odovzdá ako ďalší parameter.
  • Pomocou tejto adresy môžu metódy triedy State v prípade potreby zmeniť "aktuálny stav" triedy Context.

Vlastnosti štátneho vzoru

  • Štátne objekty sú často jednotlivé.
  • Flyweight ukazuje, ako a kedy je možné objekty štátu rozdeliť.
  • Vzor Interpreter môže použiť stav na definovanie kontextov analýzy.
  • Vzory State a Bridge majú podobné štruktúry, s výnimkou toho, že Bridge umožňuje hierarchiu tried obalov (analógy tried „obalov“), zatiaľ čo State nie. Tieto vzory majú podobnú štruktúru, ale riešia rôzne problémy: Stav umožňuje objektu meniť svoje správanie v závislosti od jeho vnútorného stavu, zatiaľ čo Bridge oddeľuje abstrakciu od jej implementácie, aby sa dali meniť nezávisle od seba.
  • Implementácia vzoru štátu je založená na vzore Stratégia. Rozdiely spočívajú v ich účele.

Implementácia štátneho vzoru

Uvažujme príklad konečného automatu s dvoma možnými stavmi a dvoma udalosťami.

#include pomocou menného priestoru std; class Machine ( class State *current; public: Machine(); void setCurrent(State *s) ( current = s; ) void on(); void off(); ); class State ( public: virtual void on(Machine *m) ( cout<< " already ON\n"; } virtual void off(Machine *m) { cout << " already OFF\n"; } }; void Machine::on() { current->Na toto); ) void Machine::off() ( current->off(this); ) class ON: public State ( public: ON() ( cout<< " ON-ctor "; }; ~ON() { cout << " dtor-ON\n"; }; void off(Machine *m); }; class OFF: public State { public: OFF() { cout << " OFF-ctor "; }; ~OFF() { cout << " dtor-OFF\n"; }; void on(Machine *m) { cout << " going from OFF to ON"; m->setCurrent(new ON()); vymazať toto; )); void ON::off (Stroj *m) ( cout<< " going from ON to OFF"; m->setCurrent(new OFF()); vymazať toto; ) Machine::Machine() ( aktuálny = nový OFF(); cout<< "\n"; } int main() { void(Machine:: *ptrs)() = { Machine::off, Machine::on }; Machine fsm; int num; while (1) { cout << "Enter 0/1: "; cin >>num; (fsm. *ptrs)(); ))

15.02.2016
21:30

Vzor Stav je určený na navrhovanie tried, ktoré majú viacero nezávislých logických stavov. Poďme rovno na príklad.

Povedzme, že vyvíjame triedu ovládania webovej kamery. Kamera môže byť v troch stavoch:

  1. Neinicializované. Nazvime to NotConnectedState ;
  2. Inicializované a pripravené na použitie, ale zatiaľ sa nezachytávajú žiadne snímky. Nech je to ReadyState ;
  3. Aktívny režim snímania snímok. Označme ActiveState .

Keďže pracujeme so stavovým vzorom, je najlepšie začať s obrázkom stavového diagramu:

Teraz premeňme tento diagram na kód. Aby sme implementáciu nekomplikovali, vynechávame kód pre prácu s webovými kamerami. V prípade potreby môžete sami pridať príslušné volania funkcií knižnice.

Okamžite poskytnem celý zoznam s minimálnymi komentármi. Ďalej budeme podrobnejšie diskutovať o kľúčových detailoch tejto implementácie.

#include #define DECLARE_GET_INSTANCE(ClassName) \ static ClassName* getInstance() (\ static ClassName instance;\ return \ ) class WebCamera ( public: typedef std::string Frame; public: // *********** ******************************************** // Výnimky // ****** ************************************************* trieda Nepodporované: verejná std: :exception ( ); public: // ************************************************* ******** ********* // Štáty // ********************************** ******** ************** class NotConnectedState; class ReadyState; class ActiveState; class State ( public: virtual ~State() ( ) virtual void connect(WebCamera*) ( throw NotSupported(); ) virtuálne neplatné odpojenie (WebCamera* cam) ( std::cout<< "Деинициализируем камеру..." << std::endl; // ... cam->changeState(NotConnectedState::getInstance()); ) virtual void start(WebCamera*) ( throw NotSupported(); ) virtual void stop(WebCamera*) ( throw NotSupported(); ) virtual Frame getFrame(WebCamera*) ( throw NotSupported(); ) protected: State() ( ) ); // ********************************************************** ** trieda NotConnectedState: public State ( public: DECLARE_GET_INSTANCE(NotConnectedState) void connect(WebCamera* cam) ( std::cout<< "Инициализируем камеру..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); ) void unlock(WebCamera*) ( throw NotSupported(); ) private: NotConnectedState() ( ) ); // ********************************************************** ** trieda ReadyState: public State ( public: DECLARE_GET_INSTANCE(ReadyState) void start(WebCamera* cam) ( std::cout<< "Запускаем видео-поток..." << std::endl; // ... cam->changeState(ActiveState::getInstance()); ) private: ReadyState() ( ) ); // ********************************************************** ** trieda ActiveState: public State ( public: DECLARE_GET_INSTANCE(ActiveState) void stop (WebCamera* cam) ( std::cout<< "Останавливаем видео-поток..." << std::endl; // ... cam-> << "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } }; public: explicit WebCamera(int camID) : m_camID(camID), m_state(NotConnectedState::getInstance()) { } ~WebCamera() { try { disconnect(); } catch(const NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } } void connect() { m_state->pripojiť (toto); ) void unlock() ( m_state->disconnect(to); ) void start() ( m_state->start(to); ) void stop() ( m_state->stop(this); ) Frame getFrame() ( return m_state ->getFrame(this); ) private: void changeState(State* newState) ( m_state = newState; ) private: int m_camID; State* m_state; );

Upozorňujem na makro DECLARE_GET_INSTANCE. Samozrejme, používanie makier v C++ sa neodporúča. To sa však týka prípadov, keď makro funguje ako analóg funkcie šablóny. V tomto prípade vždy uprednostňujte to druhé.

V našom prípade je makro určené na definovanie statickej funkcie potrebnej na implementáciu . Preto možno jeho použitie považovať za opodstatnené. Koniec koncov, umožňuje znížiť duplicitu kódu a nepredstavuje žiadne vážne hrozby.

Triedy State deklarujeme v hlavnej triede – WebCamera. Pre stručnosť som použil inline definície členských funkcií všetkých tried. V skutočných aplikáciách je však lepšie riadiť sa odporúčaniami o oddelení deklarácie a implementácie do súborov h a cpp.

Stavové triedy sú deklarované vo WebCamera, takže majú prístup k súkromným poliam tejto triedy. To samozrejme vytvára mimoriadne tesné spojenie medzi všetkými týmito triedami. Ukázalo sa však, že štáty sú také špecifické, že ich opätovné použitie v iných kontextoch neprichádza do úvahy.

Základom hierarchie stavových tried je abstraktná trieda WebCamera::State:

Stav triedy ( public: virtual ~State() ( ) virtual void connect(WebCamera*) ( throw NotSupported(); ) virtual void cancel(WebCamera* cam) ( std::cout<< "Деинициализируем камеру..." << std::endl; // ... cam->changeState(NotConnectedState::getInstance()); ) virtual void start(WebCamera*) ( throw NotSupported(); ) Virtual Void stop(WebCamera*) ( throw NotSupported(); ) virtual Frame getFrame(WebCamera*) ( throw NotSupported(); ) protected: State() ( ) );

Všetky jej členské funkcie zodpovedajú funkciám samotnej triedy WebCamera. Priame delegovanie prebieha:

Trieda WebCamera ( // ... void connect() ( m_state->connect(this); ) void unlock() ( m_state->disconnect(this); ) void start() ( m_state->start(this); ) void stop() ( m_state->stop(this); ) Frame getFrame() ( return m_state->getFrame(this); ) // ... State* m_state; )

Kľúčovou vlastnosťou je, že objekt State akceptuje ukazovateľ na inštanciu WebCamera, ktorá ho volá. To vám umožňuje mať iba tri štátne objekty pre ľubovoľne veľký počet kamier. Táto možnosť je dosiahnutá použitím vzoru Singleton. Samozrejme, v kontexte príkladu z toho nezískate významný zisk. Ale poznať túto techniku ​​je stále užitočné.

Sama o sebe trieda WebCamera nerobí prakticky nič. Je úplne závislý od svojich Štátov. A tieto štáty zase určujú podmienky vykonávania operácií a poskytujú potrebné súvislosti.

Väčšina členských funkcií WebCamera::State používa našu vlastnú WebCamera::NotSupported . Toto je úplne vhodné predvolené správanie. Ak sa napríklad niekto pokúsi inicializovať kameru, keď už bola inicializovaná, celkom prirodzene dostane výnimku.

Zároveň poskytujeme predvolenú implementáciu pre WebCamera::State::disconnect(). Toto správanie je vhodné pre dva z troch stavov. V dôsledku toho predchádzame duplicite kódu.

Ak chcete zmeniť stav, použite súkromnú členskú funkciu WebCamera::changeState() :

Void changeState(State* newState) ( m_state = newState; )

Teraz k implementácii konkrétnych štátov. Pre WebCamera::NotConnectedState stačí prepísať operácie connect() a odpojenie():

Trieda NotConnectedState: public State ( public: DECLARE_GET_INSTANCE(NotConnectedState) void connect(WebCamera* cam) ( std::cout<< "Инициализируем камеру..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); ) void unlock(WebCamera*) ( throw NotSupported(); ) private: NotConnectedState() ( ) );

Pre každý štát môžete vytvoriť jednu inštanciu. To nám zaručuje vyhlásenie súkromného konštruktéra.

Ďalším dôležitým prvkom prezentovanej implementácie je, že do nového štátu sa presťahujeme len v prípade úspechu. Ak napríklad dôjde k zlyhaniu počas inicializácie kamery, je príliš skoro na to, aby ste vstúpili do stavu ReadyState. Hlavnou myšlienkou je úplná zhoda medzi skutočným stavom kamery (v našom prípade) a objektu State.

Fotoaparát je teda pripravený. Vytvorme zodpovedajúcu triedu WebCamera::ReadyState State:

Trieda ReadyState: verejný stav ( public: DECLARE_GET_INSTANCE(ReadyState) void start (WebCamera* cam) ( std::cout<< "Запускаем видео-поток..." << std::endl; // ... cam->changeState(ActiveState::getInstance()); ) private: ReadyState() ( ) );

Z Ready State môžeme vstúpiť do aktívneho Frame Capture State. Na tento účel je poskytnutá operácia start(), ktorú sme implementovali.

Nakoniec sme dosiahli posledný logický stav kamery, WebCamera::ActiveState:

Trieda ActiveState: public State ( public: DECLARE_GET_INSTANCE(ActiveState) void stop (WebCamera* cam) ( std::cout<< "Останавливаем видео-поток..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); ) Frame getFrame(WebCamera*) ( std::cout<< "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } };

V tomto stave môžete zastaviť zachytávanie snímok pomocou stop() . V dôsledku toho sa vrátime späť do WebCamera::ReadyState. Okrem toho môžeme prijímať snímky, ktoré sa hromadia vo vyrovnávacej pamäti fotoaparátu. Pre jednoduchosť „rámčekom“ rozumieme obyčajný reťazec. V skutočnosti to bude nejaký druh bajtového poľa.

Teraz si môžeme zapísať typický príklad práce s našou triedou WebCamera:

Int main() ( WebCamera cam(0); try ( // kamera v NotConnectedState cam.connect(); // kamera v ReadyState cam.start(); // kamera v ActiveState std::cout<< cam.getFrame() << std::endl; cam.stop(); // Можно было сразу вызвать disconnect() // cam в Состоянии ReadyState cam.disconnect(); // cam в Состоянии NotConnectedState } catch(const WebCamera::NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } return 0; }

Výsledkom bude výstup do konzoly:

Inicializovať kameru... Spustiť tok videa... Získať aktuálnu snímku... Aktuálna snímka Zastaviť tok videa... Deinicializovať kameru...

Teraz skúsme vyprovokovať chybu. Zavolajme connect() dvakrát za sebou:

Int main() ( WebCamera cam(0); try ( // kamera v NotConnectedState cam.connect(); // cam v ReadyState // Ale pre tento stav nie je k dispozícii operácia connect()! cam.connect( ); // Vyvolá výnimku NotSupported ) catch(const WebCamera::NotSupported& e) ( std::cout<< "Произошло исключение!!!" << std::endl; // ... } catch(...) { // Обрабатываем исключение } return 0; }

Tu je to, čo z toho vyplýva:

Inicializuje sa fotoaparát... Nastala výnimka!!! Poďme deinicializovať fotoaparát...

Upozorňujeme, že fotoaparát bol stále deinicializovaný. V deštruktore webovej kamery sa vyskytlo volanie unlock(). Tie. vnútorný stav objektu zostáva absolútne správny.

závery

Pomocou vzoru Stav môžete jedinečne transformovať stavový diagram na kód. Na prvý pohľad sa implementácia ukázala ako podrobná. Dospeli sme však k jasnému rozdeleniu do možných kontextov pre prácu s hlavnou triedou WebCamera. Výsledkom bolo, že pri písaní každého jednotlivého štátu sme sa mohli sústrediť na úzku úlohu. A toto je najlepší spôsob, ako napísať jasný, zrozumiteľný a spoľahlivý kód.