Chcete-li správně používat vzory Stát A Strategie v jádru Java aplikací je důležité, aby programátoři Java jasně pochopili rozdíl mezi nimi. Ačkoli oba vzory, stát a strategie, mají podobnou strukturu a oba jsou založeny na principu otevřený/uzavřený, představující „O“ v principech SOLID, jsou zcela odlišné v záměry. Vzor Strategie v Javě používá se k zapouzdření související sady algoritmů pro zajištění flexibility provádění pro klienta. Klient si může za běhu vybrat libovolný algoritmus, aniž by měnil kontext třídy, která používá objekt Strategy. Některé příklady populárních vzorů Strategie je psaní kódu, který používá algoritmy, jako je šifrování, komprese nebo řazení. Na druhou stranu vzor Stav umožňuje, aby se objekt choval v různých stavech odlišně. Protože v reálném světě má objekt často stavy a chová se v něm jinak různé státy Například automat prodává zboží, pouze pokud je ve stavu hasCoin, neprodává, dokud do něj nevhodíte minci. Nyní můžete jasně vidět rozdíl mezi strategií a vzorem státu, to jsou různé záměry. Vzor Stav pomáhá řídit stav objektu, zatímco vzor Strategie umožňuje klientovi vybrat si jiné chování. Dalším rozdílem, který není tak snadné vidět, je to, kdo je hnacím motorem změny chování. V případě vzoru Strategie se jedná o klienta, který poskytuje kontextu různé strategie, u vzoru Stav je přechod řízen kontextem nebo stavem samotného objektu. Navíc, pokud sami spravujete změny stavu v objektu State, musí tam být odkaz na kontext, například prodejní automat musí být schopen zavolat metodu setState(), aby změnil aktuální stav kontextu. Na druhou stranu objekt Strategie nikdy neobsahuje odkaz na kontext, klient sám předává do kontextu strategii dle svého výběru. Rozdíl mezi vzory State a Strategy je jednou z populárních otázek rozhovoru o vzorech Java, v tomto článku o vzorech Java se na to podíváme blíže. Prozkoumáme některé podobnosti a rozdíly mezi vzory Strategie a Stav v Javě, které vám pomohou lépe porozumět těmto vzorcům.

Podobnosti mezi vzorem státu a strategie

Když se podíváte na UML diagram vzorů Stav a Strategie, všimnete si, že oba vypadají podobně. Objekt, který používá stav ke změně svého chování, se nazývá kontextový objekt, podobně objekt, který používá strategii ke změně svého chování, se nazývá kontextový objekt. Pamatujte, že klient komunikuje s objektem Context. V případě vzoru State kontext deleguje volání metod na objekt State, který je držen jako aktuální objekt, a v případě vzoru Strategy kontext používá objekt Strategy jako parametr nebo je poskytnut během vytváření. kontextu objektu. UML diagram stavového vzoru v Javě
Tento diagram UML pro vzor State zobrazuje klasický problém vytváření objektově orientovaného návrhu prodejního automatu v Javě. Můžete vidět, že stav prodejního automatu je reprezentován pomocí rozhraní, které pak má implementaci reprezentující konkrétní stav. Každý stav má také odkazy na kontext objektu pro provedení přechodu do jiného stavu v důsledku akcí vyvolaných v kontextu.
Tento diagram UML pro vzor strategie obsahuje funkční implementace druhů. Protože existuje mnoho třídicích algoritmů, tento návrhový vzor umožňuje klientovi vybrat si algoritmus při třídění objektů. Ve skutečnosti Java Collection framework používá tento vzor implementací metody Collections.sort(), která se používá k třídění objektů v Javě. Jediný rozdíl je v tom, že namísto toho, aby si klient mohl vybrat třídicí algoritmus, umožňuje mu specifikovat strategii porovnání předáním instance rozhraní Comparator nebo Comparable do Javy. Podívejme se na několik podobností mezi těmito dvěma hlavními návrhovými vzory v Javě:
  1. Oba vzory, State a Strategy, usnadňují přidání nového stavu a strategie, aniž by to ovlivnilo kontext objektu, který je používá.

  2. Oba tyto udržují váš kód podle principu otevřené/uzavřené, což znamená, že návrh bude otevřený rozšířením, ale uzavřený vůči úpravám. V případě vzorů Stav a Strategie je kontext objektu uzavřen pro modifikace, zavádění nových stavů nebo nových strategií, případně nemusíte upravovat kontext jiného stavu nebo minimální změny.

  3. Stejně jako kontext objektu začíná stavem inicializace objektu ve vzoru Stav, kontext objektu má také výchozí strategii v případě vzoru Strategie v Javě.

  4. Vzor Stav představuje různé chování ve formě různých stavů objektů, zatímco vzor Strategie představuje různé chování ve formě různých objektových strategií.

  5. Oba vzory, strategie i stav, závisí na podtřídách implementace chování. Každá konkrétní strategie rozšiřuje abstraktní strategii; každý stav je podtřídou rozhraní nebo abstraktní třídy, která se používá k reprezentaci stavu.

Rozdíly mezi strategiemi a státními vzory v Javě

Nyní tedy víme, že vzory státu a strategie mají podobnou strukturu, ale jejich záměr je odlišný. Podívejme se na některé klíčové rozdíly mezi těmito designovými vzory.
  1. Vzor Strategie zapouzdřuje sadu souvisejících algoritmů a umožňuje klientovi používat zaměnitelné chování navzdory složení a delegování za běhu, na druhou stranu vzor Stav pomáhá třídě vykazovat různé chování v různých stavech.

  2. Další rozdíl mezi vzorem Stav a Strategie je v tom, že Stát zapouzdřuje stav objektu, zatímco vzor Strategie zapouzdřuje algoritmus nebo strategii. Protože je stav spojen s objektem, nelze jej znovu použít, ale oddělením strategie nebo algoritmu od jeho kontextu jej můžeme znovu použít.

  3. Ve vzoru Stav může osobní stav obsahovat odkaz na kontext pro implementaci přechodů mezi státy, ale strategie neobsahuje odkaz na kontext, kde je použita.

  4. Implementaci strategie lze předat jako parametr objektu, který ji bude používat, například Collection.sort() vezme komparátor, což je strategie. Na druhé straně je stav součástí samotného kontextu objektu a v průběhu času se kontext objektu přechází z jednoho stavu do druhého.

  5. Ačkoli jak Strategie, tak Stát se řídí principem otevřené/uzavřené, Strategie se řídí také Principem jednotné odpovědnosti, protože každá strategie obsahuje individuální algoritmus, různé strategie jsou na sobě nezávislé. Změna jedné strategie nevyžaduje změnu strategie jiné.

  6. Další teoretický rozdíl mezi vzory Strategie a Stav je v tom, že tvůrce definuje část objektu „Jak“, např. „Jak“ třídící objekt třídí data, na druhé straně vzor Stav definuje „co“ a „kdy“. části objektu, např. co může objekt dělat, když je v určitém stavu.

  7. Pořadí stavových přechodů je dobře definováno ve vzoru Stav, u vzoru Strategie takový požadavek neexistuje. Klient si může zvolit jakoukoli implementaci Strategie dle svého výběru.

  8. Některé z běžných příkladů vzoru strategie jsou zapouzdření algoritmů, jako jsou algoritmy řazení, šifrovací algoritmy nebo kompresní algoritmy. Pokud vidíte, že váš kód by měl používat různé druhy souvisejících algoritmů, měli byste zvážit použití vzoru strategie. Na druhou stranu rozpoznání použití vzoru Stav je docela snadné, pokud potřebujete řídit přechody stavů a ​​stavů bez velkého množství vnořených podmíněných příkazů, vzor Stav je ten správný vzor.

  9. Posledním, ale jedním z nejdůležitějších rozdílů mezi vzorem Stav a Strategie je, že změnu Strategie provádí Klient, zatímco změnu Stavu může provést kontext nebo samotný stav objektu.

Všechno je to o rozdíl mezi státními a strategickými vzory v Javě. Jak jsem řekl, oba vypadají podobně ve svých třídách a UML diagramy, oba poskytují otevřený/uzavřený princip a zapouzdřují chování. Vzor Strategie použijte k zapouzdření algoritmu nebo strategie, která je vystavena kontextu za běhu, například jako parametr nebo složený objekt, a použijte vzor Stav k řízení přechodů stavů v Javě. Původní "vzor"Stát" zdroj .ru

Stav je vzorec chování objektu, který určuje různé funkce v závislosti na vnitřní stav objekt. webové stránky původní zdroj

Podmínky, úkol, účel

Umožňuje objektu měnit své chování v závislosti na jeho vnitřním stavu. Jelikož se chování může měnit zcela libovolně bez jakýchkoli omezení, zvenčí se zdá, že se změnila třída objektu.

Motivace

Zvažte třídu TCPConnection, kterým je zastoupena internetové připojení. Objekt této třídy může být v jednom z několika stavů: Založeno(instalováno), Naslouchání(Naslouchání), ZAVŘENO(ZAVŘENO). Když objekt TCPConnection přijímá požadavky od jiných objektů, reaguje různě v závislosti na aktuálním stavu. Například odpověď na žádost OTEVŘENO(otevřeno) závisí na tom, zda je připojení ve stavu ZAVŘENO nebo Založeno. Vzor stavu popisuje, jak objekt TCPConnection v různých stavech se může chovat odlišně. source.ru

Hlavní myšlenkou tohoto vzoru je představit abstraktní třídu TCPState reprezentovat různé stavy připojení. Tato třída deklaruje rozhraní, které je společné pro všechny třídy, které popisují různé pracovní zdroje.ru

stav. V těchto podtřídách TCPState je implementováno stavově specifické chování. Například ve třídách TCPEstablished A TCPClosed implementováno chování specifické pro stav Založeno A ZAVŘENO respektive. webové stránky původní zdroj

původní zdrojový web

Třída TCPConnection ukládá objekt stavu (instanci podtřídy TCPState) představující aktuální stav připojení a deleguje všechny stavově závislé požadavky na tento objekt. TCPConnection používá svou vlastní instanci podtřídy TCPState docela jednoduché: volání metod jediného rozhraní TCPState, jen podle toho, co v tento moment je uložena konkrétní podtřída TCPState-a - výsledek je jiný, tzn. ve skutečnosti se provádějí operace, které jsou specifické pouze pro tento stav připojení. zdroj original.ru

A pokaždé, když se stav připojení změníTCPConnection změní svůj stavový objekt. Například, když je navázané spojení uzavřeno, TCPConnection nahrazuje instanci třídy TCPEstablished kopírovat TCPClosed. původní zdrojový web

Známky aplikace, použití vzoru State

Vzor stavu použijte v následujících případech: zdroj original.ru
  1. Když chování objektu závisí na jeho stavu a musí se změnit za běhu. .ru
  2. Když kód operace obsahuje více větví podmíněné příkazy, ve kterém výběr pobočky závisí na státu. Typicky je v tomto případě stav reprezentován výčtovými konstantami. Často se stejná struktura podmíněného příkazu opakuje v několika operacích.Stavový vzor navrhuje umístit každou větev do samostatné třídy. To vám umožňuje zacházet se stavem objektu jako s nezávislým objektem, který se může měnit nezávisle na ostatních. zdroj original.ru

Řešení

zdroj webu původní

original.ru

Účastníci státního vzoru

zdroj original.ru
  1. Kontext(TCPConnection) - kontext.
    Definuje jediné rozhraní pro klienty.
    Ukládá instanci podtřídy ConcreteState, která určuje aktuální stav. original.ru
  2. Stát(TCPSate) - stát.
    Definuje rozhraní pro zapouzdření chování spojeného s konkrétním stavem kontextu. zdroj original.ru
  3. Podtřídy ConcreteState(TCPEstablished, TCPListen, TCPClosed) - specifický stav.
    Každá podtřída implementuje chování spojené s nějakým stavem kontextu Kontext. webové stránky původní zdroj

Schéma pro použití vzoru State

Třída Kontext deleguje požadavky na aktuální objekt ConcreteState. webové stránky původní zdroj

Kontext se může předat jako argument objektu Stát, která žádost zpracuje. To umožňuje objekt stavu ( ConcreteState) v případě potřeby přístup ke kontextu. původní zdrojový web

Kontext- Toto je hlavní rozhraní pro klienty. Klienti mohou konfigurovat kontext pomocí stavových objektů Stát(přesněji ConcreteState). Jakmile je kontext nakonfigurován, klienti již nemusejí komunikovat přímo se stavovými objekty (pouze přes společné rozhraní Stát). zdroj webu původní

V tomto případě také Kontext nebo samotné podtřídy ConcreteState může rozhodnout, za jakých podmínek a v jakém pořadí ke změně stavů dojde. zdroj .ru

Otázky týkající se implementace státního vzoru

Otázky týkající se implementace vzoru státu: zdroj original.ru
  1. Co určuje přechody mezi stavy.
    Stavový vzor nic neříká o tom, který účastník určuje podmínky (kritéria) pro přechod mezi stavy. Pokud jsou kritéria pevná, lze je implementovat přímo ve třídě Kontext. Obecně však platí, že flexibilnější a správný přístup je povolit podtřídy samotné třídy Stát určit další stav a okamžik přechodu. K tomu ve třídě Kontext musíme přidat rozhraní, které umožňuje z objektů Stát nastavit jeho stav.
    Tato decentralizovaná přechodová logika se snadněji upravuje a rozšiřuje – stačí definovat nové podtřídy Stát. Nevýhodou decentralizace je, že každá podtřída Stát musí „vědět“ alespoň o jedné podtřídě jiného stavu (do kterého může aktuálně přepnout aktuální stav), což zavádí implementační závislosti mezi podtřídami. source.ru

    zdroj webu původní
  2. Tabulková alternativa.
    Existuje další způsob, jak strukturovat státem řízený kód. Toto je princip konečného automatu. K mapování vstupů na přechody stavů používá tabulku. S jeho pomocí můžete určit, do kterého stavu musíte přejít, když dorazí určitá vstupní data. V podstatě nahrazujeme podmíněný kód vyhledáváním v tabulce.
    Hlavní výhodou stroje je jeho pravidelnost: ke změně přechodových kritérií stačí upravit pouze data, nikoli kód. Ale jsou tu i nevýhody:
    - prohledávání tabulky je často méně efektivní než volání funkce,
    - prezentace přechodové logiky v jednotném tabulkovém formátu činí kritéria méně explicitní, a proto jsou obtížnější pochopitelná,
    - obvykle je obtížné přidat akce, které doprovázejí přechody mezi stavy. Tabulková metoda zohledňuje stavy a přechody mezi nimi, je však potřeba ji doplnit, aby bylo možné provádět libovolné výpočty při každé změně stavu.
    Hlavní rozdíl mezi tabulkovými stavovými automaty a stavovým vzorem lze formulovat následovně: Stavový vzor modeluje stavově závislé chování, zatímco tabulková metoda se zaměřuje na definování přechodů mezi státy. zdroj original.ru

    originál source.ru
  3. Vytváření a ničení státních objektů.
    Během procesu vývoje si obvykle musíte vybrat mezi:
    - vytváření stavových objektů, když jsou potřeba, a jejich zničení ihned po použití,
    - vytvářet je předem a navždy.

    První možnost je výhodnější, když není předem známo, do jakých stavů systém spadne, a kontext mění stav poměrně zřídka. Zároveň nevytváříme objekty, které nebudou nikdy použity, což je důležité, pokud je ve stavových objektech uloženo mnoho informací. Pokud ke změnám stavu dochází často, takže nechcete zničit objekty, které je reprezentují (protože mohou být velmi brzy znovu potřebné), měli byste použít druhý přístup. Čas na vytváření objektů se utrácí pouze jednou, na samém začátku, a čas na ničení se nevynakládá vůbec. Je pravda, že tento přístup se může ukázat jako nepohodlný, protože kontext musí uchovávat odkazy na všechny stavy, do kterých by se systém mohl teoreticky dostat. originál source.ru

    zdroj webu původní
  4. Použití dynamické změny.
    Je možné změnit chování na vyžádání změnou třídy objektu za běhu, ale většina objektově orientovaných jazyků to nepodporuje. Výjimkou je Perl, JavaScript a další jazyky založené na skriptovacím stroji, které poskytují takový mechanismus, a proto přímo podporují Pattern State. To umožňuje objektům měnit své chování změnou kódu třídy. originál source.ru

    původní zdroj.ru

Výsledek

Výsledky použití stav vzoru: zdroj original.ru
  1. Lokalizuje chování závislé na stavu.
    A rozděluje ho na části odpovídající stavům. Vzorec stavu umístí veškeré chování spojené s konkrétním stavem samostatný objekt. Protože kód závislý na stavu je zcela obsažen v jedné z podtříd třídy Stát, pak můžete přidat nové stavy a přechody jednoduše vytvořením nových podtříd.
    Místo toho lze použít datové členy k definování vnitřních stavů a ​​poté operací objektu Kontext by tato data zkontrolovala. Ale v tomto případě by podobné podmíněné příkazy nebo větve byly rozptýleny v kódu třídy Kontext. Přidání nového stavu by však vyžadovalo změnu několika operací, což by znesnadnilo údržbu. Stavový vzor tento problém řeší, ale také vytváří další, protože chování pro různé stavy je nakonec rozděleno mezi několik podtříd. Stát. Tím se zvyšuje počet tříd. Jedna třída je samozřejmě kompaktnější, ale pokud existuje mnoho stavů, pak je takové rozdělení efektivnější, protože jinak by se člověk musel vypořádat s těžkopádnými podmíněnými příkazy.
    Je nežádoucí mít těžkopádné podmíněné příkazy, stejně jako dlouhé procedury. Jsou příliš monolitické, a proto se úprava a rozšiřování kódu stává problémem. Stavový vzor nabízí lepší způsob, jak strukturovat stavově závislý kód. Logika popisující přechody stavů již není zabalena do monolitických příkazů -li nebo přepínač, ale distribuované mezi podtřídy Stát. Zapouzdřením každého přechodu a akce do třídy se stát stává plnohodnotným objektem. To zlepšuje strukturu kódu a zpřehledňuje jeho účel. zdroj original.ru
  2. Zvýrazňuje přechody mezi stavy.
    Pokud objekt definuje svůj aktuální stav pouze z hlediska interních dat, pak přechody mezi stavy nemají žádnou explicitní reprezentaci; objevují se pouze jako přiřazení k určitým proměnným. Zavedení samostatných objektů pro různé stavy činí přechody explicitnějšími. Kromě toho předměty Stát může chránit kontext Kontext z nesouladu vnitřních proměnných, protože přechody z hlediska kontextu jsou atomické akce. Chcete-li provést přechod, musíte změnit hodnotu pouze jedné proměnné (objektové proměnné Stát ve třídě Kontext), spíše než několik. zdroj original.ru
  3. Stavové objekty lze sdílet.
    Pokud je ve stavu objektu Stát neexistují žádné proměnné instance, což znamená, že stav, který představuje, je kódován pouze samotným typem, pak mohou různé kontexty sdílet stejný objekt Stát. Když jsou státy odděleny tímto způsobem, jsou to v podstatě oportunisté (viz oportunistický vzor), kteří nemají žádný vnitřní stav, pouze chování. zdroj původního webu

Příklad

Podívejme se na implementaci příkladu ze sekce "", tzn. budování nějaké jednoduché architektury TCP spojení. Jedná se o zjednodušenou verzi TCP protokolu, samozřejmě nepředstavuje celý protokol a dokonce ani všechny stavy TCP spojení. zdroj původního webu

Nejprve si definujme třídu TCPConnection, který poskytuje rozhraní pro přenos dat a zpracovává požadavky na změnu stavu: TCPConnection. originál source.ru

V členské proměnné Stát třída TCPConnection je uložena instance třídy TCPState. Tato třída duplikuje rozhraní změny stavu definované ve třídě TCPConnection. zdroj původního webu

Zdroj original.ru

TCPConnection deleguje všechny stavově závislé požadavky na instanci uloženou ve stavu TCPState. Také ve třídě TCPConnection je tam operace ChangeState, pomocí kterého můžete do této proměnné zapsat ukazatel na jiný objekt TCPState. Konstruktor třídy TCPConnection inicializuje Stát ukazatel do uzavřeného stavu TCPClosed(definujeme jej níže). source.ru

zdroj původního webu

Každá operace TCPState přijímá instanci TCPConnection jako parametr, čímž objekt povolíte TCPState přístup k datům objektu TCPConnection a změnit stav připojení. .ru

Ve třídě TCPState implementoval výchozí chování pro všechny požadavky, které mu byly delegovány. Může také změnit stav objektu TCPConnection prostřednictvím operace ChangeState. TCPState umístěn ve stejném balení jako TCPConnection, takže má také přístup k této operaci: TCPState . webové stránky původní zdroj

source.ru

V podtřídách TCPState implementováno chování závislé na stavu. TCP spojení může být v mnoha stavech: Založeno(instalováno), Naslouchání(Naslouchání), ZAVŘENO(uzavřený) atd. a každý z nich má svou vlastní podtřídu TCPState. Pro jednoduchost se budeme podrobně zabývat pouze 3 podtřídami - TCPEstablished, TCPListen A TCPClosed. zdroj webu původní

původní zdroj.ru

V podtřídách TCPState implementuje stavově závislé chování pro ty požadavky, které jsou v daném stavu platné. zdroj original.ru

zdroj původního webu

Po provedení akcí specifických pro stav tyto operace původní zdrojový web

způsobit ChangeState změnit stav objektu TCPConnection. Sám o protokolu TCP nemá žádné informace. Jedná se o podtřídy TCPState definovat přechody mezi stavy a akcemi diktovanými protokolem. webové stránky původní zdroj

Ru originál

Známé aplikace státního vzoru

Ralph Johnson a Jonathan Zweig charakterizují stavový vzorec a popisují jej ve vztahu k protokolu TCP.
Nejoblíbenější interaktivní kreslicí programy poskytují „nástroje“ pro provádění přímých manipulačních operací. Například nástroj pro kreslení čar umožňuje uživateli kliknout myší do libovolného bodu a poté pohybem myši nakreslit čáru z tohoto bodu. Nástroj pro výběr umožňuje vybrat některé tvary. Obvykle jsou všechny dostupné nástroje umístěny v paletě. Úkolem uživatele je vybrat a použít nástroj, ale ve skutečnosti se chování editoru mění podle toho, jak se nástroj mění: pomocí kreslicího nástroje vytváříme tvary, nástrojem pro výběr je vybíráme a tak dále. zdroj original.ru

Chcete-li odrážet závislost chování editoru na aktuálním nástroji, můžete použít vzor stavu. webové stránky původní zdroj

Můžete definovat abstraktní třídu Nástroj, jehož podtřídy implementují chování specifické pro nástroj. V grafickém editoru je uložen odkaz na aktuální objekt Takél a deleguje na něj příchozí požadavky. Když vyberete nástroj, editor použije jiný objekt, což způsobí změnu chování. source.ru

Tato technika se používá v rámech grafické editory HotDraw a Unidraw. Umožňuje zákazníkům snadno definovat nové typy nástrojů. V HotDraw Třída DrawingController předává požadavky na aktuální objekt Nástroj. V Unidraw jsou volány odpovídající třídy Divák A Nástroj. Níže uvedený diagram tříd poskytuje schematické znázornění rozhraní tříd Nástroj

zdroj webu původní

Účel státního vzoru

  • Vzor Stav umožňuje objektu měnit své chování v závislosti na jeho vnitřním stavu. Zdá se, že objekt změnil svou třídu.
  • Vzor Stav je objektově orientovaná implementace stavového stroje.

Problém k vyřešení

Chování objektu závisí na jeho stavu a musí se měnit během provádění programu. Takové schéma lze implementovat pomocí mnoha podmíněných operátorů: na základě analýzy aktuálního stavu objektu se provádějí určité akce. Nicméně, když velké číslo podmíněné příkazy budou rozptýleny po celém kódu a takový program bude obtížné udržovat.

Diskuse o vzoru státu

Vzor State řeší tento problém následovně:

  • Zavádí třídu Context, která definuje rozhraní k vnějšímu světu.
  • Zavádí abstraktní třídu State.
  • Představuje různé "stavy" stavového stroje jako podtřídy Stav.
  • Třída Context má ukazatel na aktuální stav, který se změní, když se změní stav stavového stroje.

Vzor Stav nedefinuje, kde přesně je určena podmínka pro přechod do nového stavu. Existují dvě možnosti: třída Context nebo podtřídy State. Výhoda poslední možnost je snadné přidávání nových odvozených tříd. Nevýhodou je, že každá podtřída State musí vědět o svých sousedech, aby mohla provést přechod do nového stavu, což zavádí závislosti mezi podtřídami.

Existuje také alternativní přístup založený na tabulkách k navrhování konečných automatů, založený na použití tabulky, která jedinečně mapuje vstupní data na přechody mezi stavy. Tento přístup má však nevýhody: je obtížné přidat provádění akcí při provádění přechodů. Přístup podle vzoru stavu používá kód (místo datových struktur) k provádění přechodů mezi stavy, takže tyto akce lze snadno přidat.

Struktura státního vzoru

Třída Context definuje přední konec pro klienty a ukládá odkaz na aktuální stav objektu State. Rozhraní abstraktní základní třídy State opakuje kontextové rozhraní s jednou výjimkou doplňkový parametr- ukazatel na instanci Context. Stavově odvozené třídy definují stavově specifické chování. Třída obálky Context deleguje všechny přijaté požadavky na objekt "aktuálního stavu", který může použít přijatý další parametr pro přístup k instanci Context.

Vzor Stav umožňuje objektu měnit své chování v závislosti na jeho vnitřním stavu. Podobný obrázek lze pozorovat i při provozu automatu. Stroje mohou mít různé stavy v závislosti na dostupnosti zboží, množství přijatých mincí, schopnosti směňovat peníze atd. Poté, co si kupující produkt vybral a zaplatil, jsou možné následující situace (stavy):

  • Zboží předejte kupujícímu, změna není nutná.
  • Dejte kupujícímu zboží a vyměňte.
  • Kupující neobdrží zboží z důvodu nedostatku peněz.
  • Kupující zboží nepřevezme z důvodu jeho nepřítomnosti.

Použití státního vzoru

  • Definujte existující nebo vytvořte novou třídu Context wrapper, kterou bude klient používat jako "stavový stroj".
  • Vytvořte základní třídu State, která replikuje rozhraní třídy Context. Každá metoda přebírá jeden další parametr: instanci třídy Context. Třída State může definovat jakékoli užitečné "výchozí" chování.
  • Vytvořte třídy odvozené od stavu pro všechny možné stavy.
  • Třída obálky Context má odkaz na objekt aktuálního stavu.
  • Třída Context jednoduše deleguje všechny požadavky přijaté od klienta na objekt „aktuální stav“, přičemž adresa objektu Context je předána jako další parametr.
  • Pomocí této adresy mohou metody třídy State v případě potřeby změnit "aktuální stav" třídy Context.

Vlastnosti státního vzoru

  • Stavové objekty jsou často singletony.
  • Flyweight ukazuje, jak a kdy lze objekty Stav rozdělit.
  • Vzor Interpreter může použít stav k definování kontextů analýzy.
  • Vzory State a Bridge mají podobnou strukturu, kromě toho, že Bridge umožňuje hierarchii tříd obálky (analogy tříd „obalu“), zatímco State nikoli. Tyto vzory mají podobnou strukturu, ale řeší různé problémy: State umožňuje objektu měnit své chování v závislosti na jeho vnitřním stavu, zatímco Bridge odděluje abstrakci od jeho implementace, takže je lze měnit nezávisle na sobě.
  • Implementace vzoru státu je založena na vzoru Strategie. Rozdíly spočívají v jejich účelu.

Implementace státního vzoru

Uvažujme příklad konečného automatu se dvěma možnými stavy a dvěma událostmi.

#zahrnout pomocí jmenného prostoru 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()); smazat toto; )); void ON::off(Stroj *m) ( cout<< " going from ON to OFF"; m->setCurrent(new OFF()); smazat toto; ) Machine::Machine() ( aktuální = nové VYPNUTO(); 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)(); ))

Behaviorální návrhový vzor. Používá se v případech, kdy během provádění programu musí objekt změnit své chování v závislosti na svém stavu. Klasická implementace zahrnuje vytvoření základní abstraktní třídy nebo rozhraní obsahující všechny metody a jednu třídu pro každý možný stav. Vzor je speciálním případem doporučení „nahradit podmíněné příkazy polymorfismem“.

Zdálo by se, že vše je podle knihy, ale je tu nuance. Jak správně implementovat metody, které nejsou pro daný stát relevantní? Jak například odebrat položku z prázdného košíku nebo zaplatit za prázdný košík? Každá třída stavu obvykle implementuje pouze relevantní metody a v ostatních případech vyvolá výjimku InvalidOperationException.

Porušení zásady nahrazení osoby Liskovem. Yaron Minsky navrhl alternativní přístup: učinit nezákonné státy nereprezentativními. To umožňuje přesunout kontrolu chyb z runtime do doby kompilace. Řídicí tok však v tomto případě bude organizován na základě shody vzorů a nikoli pomocí polymorfismu. Naštěstí, .

Další podrobnosti o příkladu tématu F# učinit nezákonné státy nereprezentativními odhaleno na webových stránkách Scotta Vlashina.

Zvažme implementaci „stavu“ na příkladu koše. C# nemá vestavěný sjednocovací typ. Oddělme data a chování. Samotný stav zakódujeme pomocí enum a chování jako samostatnou třídu. Pro usnadnění deklarujme atribut spojující výčet a odpovídající třídu chování, základní třídu „stav“ a přidejte metodu rozšíření pro přechod z výčtu do třídy chování.

Infrastruktura

public class StateAttribute: Atribut ( public Type StateType ( get; ) public StateAttribute(Type stateType) ( StateType = stateType ?? throw new ArgumentNullException(nameof(stateType)); ) ) public abstract class State kde T: třída ( chráněný stav (entita T) ( Entita = entita ?? vyvolání nového ArgumentNullException (jméno(entity)); ) chráněná entita T ( get; ) ) veřejná statická třída StateCodeExtensions ( veřejný statický stav Do stavu (tento Enum stateCode, objektová entita) kde T: třída // ano, ano odraz je pomalý. Nahraďte zkompilovaným stromem výrazů // nebo IL Emit a bude to rychlé => (State ) Activator.CreateInstance(stateCode .GetType() .GetCustomAttribute ().StateType, entita); )

Předmětná oblast

Pojďme deklarovat entitu „košík“:

Veřejné rozhraní IHasState kde TEntity: class ( TStateCode StateCode ( get; ) State State ( get; ) ) veřejná částečná třída Košík: IHasState ( public User User ( get; protected set; ) public CartStateCode StateCode ( get; protected set; ) public State State => StateCode.ToState (tento); public decimal Celkem ( get; protected set; ) chráněná virtuální ICollection Produkty (get; set; ) = nový seznam (); // Pouze ORM chráněný Cart() ( ) public Cart(User user) ( User = user ?? vyvolání nového 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, desetinný součet): this(uživatel, produkty) (pokud (celkem<= 0) { throw new ArgumentException(nameof(total)); } Total = total; } }
Implementujeme jednu třídu pro každý stav košíku: prázdný, aktivní a zaplacený, ale nebudeme deklarovat společné rozhraní. Ať každý stát implementuje pouze relevantní chování. To neznamená, že třídy EmptyCartState, ActiveCartState a PaidCartState nemohou všechny implementovat stejné rozhraní. Mohou, ale takové rozhraní musí obsahovat pouze metody, které jsou dostupné v každém státě. V našem případě je metoda Add dostupná v EmptyCartState a ActiveCartState, takže je můžeme zdědit z abstraktní AddableCartStateBase. Položky však můžete přidávat pouze do nezaplaceného košíku, nebude tedy společné rozhraní pro všechny státy. Tímto způsobem zaručujeme, že v našem kódu v době kompilace není žádná InvalidOperationException.

Veřejná částečná třída Cart ( veřejný výčet CartStateCode: byte ( Prázdný, Aktivní, Placený ) veřejné rozhraní IAddableCartState ( ActiveCartState Add(produkt produktu); IEnumerable Produkty ( get; ) ) veřejné rozhraní INotEmptyCartState ( IEnumerable Produkty ( get; ) decimal Total ( get; ) ) public abstract class AddableCartState: State , IAddableCartState ( chráněný AddableCartState(entita košíku): základ(entita) ( ) public ActiveCartState Add(produkt produktu) ( Entity.Products.Add(produkt); Entity.StateCode = CartStateCode.Active; vrátit (ActiveCartState)Entity.State; ) veřejné IEpočetné Produkty => Entity.Products; ) veřejná třída EmptyCartState: AddableCartState ( public EmptyCartState(entita košíku): základní(entita) ( ) ) veřejná třída ActiveCartState: AddableCartState, INotEmptyCartState ( public ActiveCartState(entita košíku): základ(entita) ( ) public PaidCartState Pay(deci Entity.Total = celkem; Entity.StateCode = CartStateCode.Paid; return (PaidCartState)Entity.State; ) public State 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); ) veřejná třída PaidCartState: State , INotEmptyCartState ( public IEnumerable Produkty => Entity.Products; veřejné desetinné Celkem => Entita.Celkem; public PaidCartState(entita košíku) : základna(entita) ( ) ) )
Státy jsou deklarovány jako vnořené ( vnořené) třídy nejsou náhodné. Vnořené třídy mají přístup k chráněným členům třídy Cart, což znamená, že nemusíme obětovat zapouzdření entit, abychom implementovali chování. Abych nezahltil soubor třídy entity, rozdělil jsem deklaraci na dvě: Cart.cs a CartStates.cs pomocí klíčové slovočásteč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); výchozí: vyvolání nové InvalidOperationException(); ) )
V závislosti na stavu vozíku použijeme různé pohledy. U prázdného košíku zobrazíme zprávu „váš košík je prázdný“. Aktivní košík bude obsahovat seznam produktů, možnost změnit počet produktů a některé z nich odebrat, tlačítko „objednat“ a celkovou částku nákupu.

Placený košík bude vypadat stejně jako aktivní košík, ale bez možnosti cokoliv upravovat. Tuto skutečnost lze zaznamenat zvýrazněním rozhraní INotEmptyCartState. Tím jsme se nejen zbavili porušování principu Liskovovy substituce, ale uplatnili jsme i princip oddělení rozhraní.

Závěr

V kódu aplikace můžeme pracovat s odkazy rozhraní IAddableCartState a INotEmptyCartState a znovu použít kód zodpovědný za přidávání položek do košíku a zobrazování položek v košíku. Věřím, že porovnávání vzorů je vhodné pouze pro řízení toku v C#, když mezi typy není nic společného. V ostatních případech je práce se základním odkazem pohodlnější. Podobnou techniku ​​lze použít nejen pro kódování chování entity, ale také pro .

Umožňuje objektu měnit své chování v závislosti na jeho vnitřním stavu. Zvenčí se zdá, že se třída objektu změnila.

Vzor „stav“ zahrnuje přidělení základní třídy nebo rozhraní pro všechny platné operace a dědice pro každý možný stav

Kdy použít státní vzor

    Když chování objektu musí záviset na jeho stavu a může se dynamicky měnit za běhu

    Když kód metod objektu používá četné podmíněné konstrukce, jejichž výběr závisí na aktuálním stavu objektu

UML diagram vzoru "Stát":

Implementace vzoru "stav" v C#

pomocí systému; jmenný prostor DoFactory.GangOfFour.State.Structural ( /// /// Spouštěcí třída MainApp pro Structural /// State Design Pattern. /// třída MainApp ( /// /// Vstupní bod do konzolové aplikace. /// static void Main() ( // Nastavení kontextu ve stavu Context c = new Context(new ConcreteStateA()); // Vydání požadavků, které přepíná stav c.Request(); c.Request(); c.Request() ; c.Request(); // Počkejte na uživatele Console.ReadKey(); ) ) /// /// Abstraktní třída "Stát" /// abstraktní třída State ( public abstract void Handle(Context context); ) /// class ConcreteStateA: State ( public override void Handle(Context context) ( context.State = new ConcreteStateB(); ) ) /// /// Třída "ConcreteState" /// class ConcreteStateB: State ( public override void Handle(Context context) ( context.State = new ConcreteStateA(); ) ) /// /// Třída "Kontext" /// class Context ( private State _state; // Konstruktor public Context(State state) ( this.State = state; ) // Získá nebo nastaví stav public State State ( get ( return _state; ) set ( _state = value; Console.WriteLine ("State: " + _state.GetType().Name); ) ) public void Request() ( _state.Handle(this); ) ) )

Příklad státního vzoru ze skutečného života

Příklady v .NET Framework

  • CommunicationObject implementuje státní stroj přechod mezi stavy klienta WCF: Vytvořeno, Otevřeno, Otevřeno, Zavřeno, Zavřeno a Chybné.
  • Task implementuje konečný automat pro přechod mezi stavy úlohy: Created, WaitingForActivation, WaitingToRun, Running, Run ToCompletion, Canceled, Faulted.