Uveďte príklad na str. Štátny vzor

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. Kľúčové detaily tejto implementácie podrobnejšie rozoberieme nižšie.

#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: :výnimka ( ); // ********************************************** ******** ********* // Štáty // ********************************** ******** ************** class NotConnectedState class State ( public: virtual ~State() ( ) virtual void connect(WebCamera*) ( throw NotSupported(); ) virtuálne void odpojiť (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 zabraňujeme 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 fotoaparátu, 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 objektom 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.

Štát je vzorec správania, ktorý vám umožňuje dynamicky meniť správanie objektu pri zmene jeho stavu.

Správanie špecifické pre štát sa presúva do oddelených tried. Pôvodná trieda ukladá odkaz na jeden z týchto stavových objektov a deleguje naň prácu.

Vlastnosti vzoru v Jave

zložitosť:

Popularita:

Použiteľnosť: Vzor State sa v Jave často používa na premenu ťažkopádnych stavových strojov vytvorených na príkazoch switch na objekty.

Príklady stavu v štandardných Java knižniciach:

  • javax.faces.lifecycle.LifeCycle#execute() (riadené z FacesServlet: správanie závisí od aktuálnej fázy JSF)

Známky používania vzoru: Metódy triedy delegujú prácu na jeden vnorený objekt.

Audio prehrávač

Trieda hlavného hráča mení svoje správanie v závislosti od toho, v akom stave sa hráč nachádza.

štátov

štáty/State.java: Rozhranie spoločného stavu

package site.state.example..state.example.ui.Player; /** * Spoločné rozhranie pre všetky stavy. */ verejná abstraktná trieda State ( Player player; /** * Kontext prechádza do konštruktora stavu, takže štát môže * v prípade potreby pristupovať k svojim dátam a metódam v budúcnosti. */ State(Player player) ( this.player = player ) public abstract String onLock();

štáty/LockedState.java: Stav "uzamknuté"

package site.state.example..state.example.ui.Player; /** * Konkrétne stavy implementujú metódy abstraktného stavu vlastným spôsobom. */ verejná trieda LockedState rozširuje stav ( LockedState(hráč hráča) ( super(hráč); player.setPlaying(false); ) @Override public String onLock() ( if (player.isPlaying()) ( player.changeState(new ReadyState) (prehrávač)); return "Prestať hrať" ) else ( return "Zamknuté..."; ) ) @Prepísať verejný reťazec onPlay() ( player.changeState(new ReadyState(player)); return "Ready"; ) @ Override public String onNext() ( return "Locked..."; ) @Override public String onPrevious() ( return "Locked..."; ) )

štáty/ReadyState.java: Pripravený stav

package site.state.example..state.example.ui.Player; /** * Môžu tiež preniesť kontext do iných stavov. */ verejná trieda ReadyState rozširuje stav ( public ReadyState(hráč hráča) ( super(hráč); ) @Override public String onLock() ( player.changeState(new LockedState(player)); return "Locked..."; ) @ Override public String onPlay() ( String action = player.startPlayback(); player.changeState(new PlayingState(player)); return action; ) @Override public String onNext() ( return "Locked..."; ) @Override public String onPrevious() ( return "Locked..."; ) )

štáty/PlayingState.java: Stav „Hranie“.

package site.state.example..state.example.ui.Player; verejná trieda PlayingState rozširuje stav ( PlayingState(hráč hráča) ( super(hráč); ) @Override public String onLock() ( player.changeState(new LockedState(player)); player.setCurrentTrackAfterStop(); return "Stop playing"; ) @Override public String onPlay() ( player.changeState(new ReadyState(player)); return "Paused..."; ) @Override public String onNext() ( return player.nextTrack(); ) @Override public String onPrevious( ) ( return player.previousTrack(); ) )

ui

ui/Player.java: Hráč

package site.state.example..state.examples.states..state.examples.states.State; import java.util.ArrayList; import java.util.List; verejná trieda hráča (privátny stav; súkromné ​​boolovské hranie = nepravda; súkromný zoznam playlist = nový ArrayList<>(); private int currentTrack = 0; public Player() ( this.state = new ReadyState(this); setPlaying(true); for (int i = 1; i<= 12; i++) { playlist.add("Track " + i); } } public void changeState(State state) { this.state = state; } public State getState() { return state; } public void setPlaying(boolean playing) { this.playing = playing; } public boolean isPlaying() { return playing; } public String startPlayback() { return "Playing " + playlist.get(currentTrack); } public String nextTrack() { currentTrack++; if (currentTrack >playlist.size() - 1) ( currentTrack = 0; ) return "Playing " + playlist.get(currentTrack); ) public String previousTrack() ( currentTrack--; if (currentTrack< 0) { currentTrack = playlist.size() - 1; } return "Playing " + playlist.get(currentTrack); } public void setCurrentTrackAfterStop() { this.currentTrack = 0; } }

ui/UI.java: GUI prehrávača

package site.state.example.ui; import javax.swing.*; import java.awt.*; public class UI ( súkromný prehrávač prehrávača; súkromné ​​statické JTextField textField = nové JTextField(); verejné používateľské rozhranie (prehrávač prehrávača) ( this.player = prehrávač; ) public void init() ( rámec JFrame = nový JFrame("Testovací prehrávač"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) kontext.setLayout(new BoxLayout.Y_AXIS));add(contextFlowLayout.CENTER)); (textField context.add(buttons) // Kontext bude reagovať na vstup používateľa // namiesto seba Reakcia sa môže líšiť v závislosti od toho, aký // stav je aktuálne aktívny. "Play" play.addActionListener(e -> textField.setText(player.getState().onPlay()) (e -> textField.setText(player.getState().onLock())); = new JButton("Next" next.addActionListener(e -> textField.setText(player.getState().onNext( ))); JButton prev = new JButton("Pred"); prev.addActionListener(e -> textField.setText(player.getState().onPrevious())); frame.setVisible(true); frame.setSize(300, 100); buttony.add(play); buttony.add(stop); buttony.add(next); buttony.add(prev); ))

Demo.java: Kód klienta

package refactoring_guru.state..state.example.ui..state.example.ui.UI; /** * Demo trieda. Tu sa to všetko spája. */ public class Demo ( public static void main(String args) ( Player player = nový hráč(); UI ui = new UI(player); ui.init(); ) )

Štát je behaviorálny návrhový vzor, ​​ktorý umožňuje objektom meniť správanie v závislosti od ich stavu. Zvonku sa zdá, že trieda objektu sa zmenila.

Problém

Vzor stavu nemožno posudzovať izolovane od konceptu stavového automatu, známeho aj ako štátny automat alebo štátny automat.


Hlavnou myšlienkou je, že program môže byť v jednom z niekoľkých stavov, ktoré sa neustále menia. Množina týchto stavov, ako aj prechodov medzi nimi, je vopred určená a konečný. Keďže je program v rôznych stavoch, môže reagovať odlišne na rovnaké udalosti, ktoré sa mu dejú.

Tento prístup je možné aplikovať na jednotlivé objekty. Napríklad objekt dokumentu môže mať tri stavy: Koncept, Moderovanie alebo Zverejnené. V každom z týchto stavov bude metóda zverejnenia fungovať inak:

  • Z konceptu odošle dokument na moderovanie.
  • Od moderovania po zverejnenie, ale za predpokladu, že to urobil administrátor.
  • V zverejnenom stave metóda neurobí nič.

Stavový automat sa najčastejšie implementuje pomocou množiny podmienených príkazov if alebo switch , ktoré kontrolujú aktuálny stav objektu a vykonávajú vhodné správanie. Pravdepodobne ste už vo svojom živote implementovali aspoň jeden štátny automat bez toho, aby ste o tom vedeli. Čo poviete na tento kód, zdá sa vám povedomý?

Trieda Dokument je stav poľa: reťazec // ... metóda publish() je prepínač (stav) "návrh": stav = "moderovanie" prerušenie "moderovanie": if (currentUser.role == "admin") stav = "zverejnené " break "published": // Nerobte nič. prestávka // ...

Hlavný problém takéhoto stavového automatu sa objaví, ak sa do dokumentu pridá tucet ďalších stavov. Každá metóda bude pozostávať z vážneho podmieneného príkazu, ktorý cyklicky prechádza dostupnými stavmi. Tento typ kódu je mimoriadne náročný na údržbu. Najmenšia zmena v logike prechodu vás prinúti dvakrát skontrolovať fungovanie všetkých metód, ktoré obsahujú operátory podmieneného stavu.

Zmätok a hromadenie podmienok sa prejavuje najmä v starších projektoch. Množinu možných stavov môže byť ťažké vopred určiť, preto sa neustále pridávajú, keď sa program vyvíja. Z tohto dôvodu sa riešenie, ktoré na samom začiatku vývoja vyzeralo jednoducho a efektívne, môže neskôr stať projekciou veľkého cestovinového monštra.

Riešenie

Vzor Stav navrhuje vytvorenie samostatných tried pre každý stav, v ktorom sa objekt môže nachádzať, a potom zahrnutie správania, ktoré zodpovedá týmto stavom.

Namiesto uloženia kódu pre všetky stavy pôvodný objekt tzv kontext, bude obsahovať odkaz na jeden zo stavových objektov a delegovať naň prácu, ktorá závisí od stavu.


Vďaka tomu, že objekty stavu budú mať spoločné rozhranie, kontext bude môcť delegovať prácu na stav bez toho, aby bol viazaný na jeho triedu. Správanie kontextu je možné kedykoľvek zmeniť pripojením iného stavového objektu k nemu.

Veľmi dôležitou nuansou, ktorá odlišuje tento vzor od stratégie, je to, že kontext aj samotné konkrétne štáty môžu o sebe vedieť a iniciovať prechody z jedného stavu do druhého.

Analógia zo života

Váš smartfón sa správa odlišne v závislosti od jeho aktuálneho stavu:

  • Keď je telefón odomknutý, stlačenie tlačidiel telefónu spôsobí určitú akciu.
  • Keď je telefón uzamknutý, stlačením tlačidiel sa dostanete na obrazovku odomknutia.
  • Keď je telefón vybitý, stlačením tlačidiel sa dostanete na obrazovku nabíjania.

Štruktúra



    Kontext ukladá odkaz na objekt stavu a deleguje naň časť práce, ktorá závisí od stavov. Kontext interaguje s týmto objektom prostredníctvom rozhrania spoločného stavu. Kontext musí mať metódu na priradenie nového objektu stavu.

    Štát popisuje spoločné rozhranie pre všetky špecifické stavy.

    Špecifické podmienky implementovať správanie spojené s určitým stavom kontextu. Niekedy musíte vytvoriť celé hierarchie stavových tried, aby ste zovšeobecnili duplicitný kód.

    Kontext aj objekty konkrétneho stavu môžu rozhodnúť, kedy a aký ďalší stav bude vybraný. Ak chcete prepnúť stav, musíte do kontextu vložiť iný objekt stavu.

Pseudokód

V tomto príklade vzor Štát zmení funkčnosť rovnakých ovládacích prvkov prehrávača hudby v závislosti od toho, v akom stave sa prehrávač práve nachádza.


Príklad zmeny správania hráčov pomocou stavov.

Objekt prehrávača obsahuje objekt stavu, ktorému deleguje hlavnú prácu. Zmenou stavov môžete zmeniť správanie ovládacích prvkov prehrávača.

// Spoločné rozhranie pre všetky stavy. abstraktná trieda State is protected field player: AudioPlayer // Kontext sa odovzdá konštruktorovi stavu, takže // štát môže v prípade potreby // v budúcnosti pristupovať k svojim údajom a metódam. konstruktor State(player) is this.player = player abstract method clickLock() abstraktná metóda clickPlay() abstraktná metóda clickNext() abstraktná metóda clickPrevious() // Konkrétne stavy implementujú metódy abstraktného stavu // vlastným spôsobom. class LockedState extends Stav je // Pri odomykaní prehrávača so zamknutými // kľúčmi môže nadobudnúť jeden z dvoch stavov. metóda clickLock() je if (player.playing) player.changeState(new PlayingState(player)) else player.changeState(new ReadyState(player)) method clickPlay() is // Nerobiť nič. metóda clickNext() je // Nerobiť nič. metóda clickPrevious() je // Nerobiť nič. // Konkrétne stavy samy môžu preniesť kontext do iného // stavu. class ReadyState extends State je metóda clickLock() je player.changeState(new LockedState(player)) metóda clickPlay() je player.startPlayback() player.changeState(new PlayingState(player)) metóda clickNext() je player.nextSong() metóda clickPrevious() je player.previousSong() class PlayingState extends State je metóda clickLock() je player.changeState(new LockedState(player)) metóda clickPlay() je player.stopPlayback() player.changeState(new ReadyState(player)) metóda clickNext() je if (event.doubleclick) player.nextSong() else player.fastForward(5) metóda clickPrevious() je if (event.doubleclick) player.previous() else player.rewind(5) // Prehrávač funguje ako kontext. class AudioPlayer je stav poľa: Pole stavu UI, hlasitosť, zoznam skladieb, konštruktor aktuálnej skladby AudioPlayer() is this.state = new ReadyState(this) // Kontext spôsobí, že stav bude reagovať na // vstup používateľa namiesto seba. Odpoveď sa môže // líšiť v závislosti od toho, aký stav je // momentálne aktívny. UI = new UserInterface() UI.lockButton.onClick(this.clickLock) UI.playButton.onClick(this.clickPlay) UI.nextButton.onClick(this.clickNext) UI.prevButton.onClick(this.clickPrevious) // Iné objekty by tiež mal byť schopný nahradiť // stav prehrávača. metóda changeState(state: State) is this.state = state // Metódy UI budú delegovať prácu na aktívny stav. metóda clickLock() je stav.clickLock() metóda clickPlay() je stav.clickPlay() metóda clickNext() je stav.clickNext() metóda clickPrevious() je stav.clickPrevious() // Metódy kontextovej služby volané stavmi. metóda startPlayback() je // ... metóda stopPlayback() je // ... metóda nextSong() je // ... metóda previousSong() je // ... metóda fastForward(time) je // .. metóda pretáčania (čas) je // ...

Použiteľnosť

Keď máte objekt, ktorého správanie sa dramaticky mení v závislosti od jeho vnútorného stavu a existuje veľa typov stavov a ich kód sa často mení.

Vzorec navrhuje rozdeliť všetky polia a metódy spojené s určitými stavmi do ich vlastných tried. Pôvodný objekt bude neustále odkazovať na jeden zo stavových objektov a delegovať naň časť svojej práce. Na zmenu stavu bude stačiť dosadiť do kontextu iný objekt stavu.

Keď kód triedy obsahuje veľa veľkých, podobných podmienených príkazov, ktoré vyberajú správanie v závislosti od aktuálnych hodnôt polí triedy.

Vzorec navrhuje presunúť každú vetvu takéhoto podmieneného príkazu do vlastnej triedy. Tu môžete pridať aj všetky polia priradené k tomuto stavu.

Keď zámerne používate stavový automat založený na tabuľkách, postavený na podmienených príkazoch, ale ste nútení zmieriť sa s duplikáciou kódu pre podobné stavy a prechody.

Vzor Stav vám umožňuje implementovať hierarchický stavový stroj založený na dedičnosti. Môžete zdediť podobné stavy z jednej nadradenej triedy a presunúť tam všetok duplicitný kód.

Kroky implementácie

    Rozhodnite sa pre triedu, ktorá bude zohrávať úlohu kontextu. Môže to byť buď existujúca trieda, ktorá už má závislosť od stavu, alebo nová trieda, ak je kód stavu rozptýlený vo viacerých triedach.

    Vytvorte rozhranie spoločného stavu. Musí popisovať metódy spoločné pre všetky stavy nachádzajúce sa v kontexte. Všimnite si, že nie všetko kontextové správanie sa musí preniesť do stavu, iba to, ktoré závisí od stavov.

    Pre každý skutočný stav vytvorte triedu, ktorá implementuje rozhranie stavu. Presuňte kód súvisiaci s konkrétnymi stavmi do príslušných tried. Koniec koncov, všetky metódy stavového rozhrania musia byť implementované vo všetkých stavových triedach.

    Pri presúvaní správania z kontextu môžete zistiť, že správanie závisí od súkromných polí alebo metód kontextu, ktoré nie sú prístupné z objektu stavu. Existuje niekoľko spôsobov, ako tento problém obísť.

    Najjednoduchšie je ponechať správanie v kontexte a volať ho zo stavu objektu. Na druhej strane môžete stavové triedy vnoriť do kontextovej triedy a potom budú mať prístup ku všetkým súkromným častiam kontextu. Posledná metóda je však dostupná iba v niektorých programovacích jazykoch (napríklad Java, C#).

    Vytvorte pole v kontexte na ukladanie stavových objektov, ako aj verejnú metódu na zmenu hodnoty tohto poľa.

    Nahraďte staré kontextové metódy, ktoré obsahovali kód závislý od stavu, volaniami zodpovedajúcich metód objektu stavu.

    V závislosti od vašej obchodnej logiky umiestnite kód, ktorý prepína stav kontextu, do kontextu alebo do konkrétnych tried stavu.

Výhody a nevýhody

  • Eliminuje veľa veľkých príkazov podmieneného stroja.
  • . Oba vzory používajú kompozíciu na zmenu správania hlavného objektu delegovaním práce na vnorené pomocné objekty. Avšak v Stratégie tieto objekty o sebe nevedia a nie sú nijako prepojené. IN Podmienka Samotné konkrétne stavy môžu zmeniť kontext.

Posledná aktualizácia: 31. 10. 2015

Stav je návrhový vzor, ​​ktorý umožňuje objektu meniť svoje správanie v závislosti od jeho vnútorného stavu.

Kedy sa tento vzor používa?

    Keď správanie objektu musí závisieť od jeho stavu a môže sa dynamicky meniť za behu

    Keď kód metód objektu používa množstvo podmienených konštruktov, ktorých výber závisí od aktuálneho stavu objektu

Diagram UML tohto vzoru návrhu navrhuje nasledujúci systém:

Formálna definícia vzoru v C#:

Class Program ( static void Main() ( Context context = new Context(new StateA()); context.Request(); // Prejsť na StateB context.Request(); // Prejsť na StateA ) ) abstraktná trieda State ( public abstract void Handle(Context context ) class StateA: State ( public override void Handle(Context context) ( context.State = new StateB(); ) ) class StateB: State ( public override void Handle(Context context) ( kontext. State = new StateA( ) class Context ( public State State ( get; set; ) public Context(State state) ( this.State = state; ) public void Request() ( this.State.Handle(this ); ); )

Účastníci vzoru

    State : definuje rozhranie stavu

    Triedy StateA a StateB sú konkrétne implementácie stavov

    Kontext: Predstavuje objekt, ktorého správanie by sa malo dynamicky meniť podľa stavu. Vykonávanie konkrétnych akcií je delegované na objekt stavu

Napríklad voda môže byť v niekoľkých skupenstvách: pevná látka, kvapalina, para. Povedzme, že potrebujeme definovať triedu Voda, ktorá by mala metódy na ohrev a zmrazovanie vody. Bez použitia vzoru State by sme mohli napísať nasledujúci program:

Program triedy ( static void Main(string args) ( Water water = new Water(WaterState.LIQUID); water.Heat(); water.Frost(); water.Frost(); Console.Read(); ) ) enum WaterState ( SOLID, LIQUID, GAS ) class Water ( public WaterState State ( get; set; ) public Water(WaterState ws) ( State = ws; ) public void Heat() ( if(State==WaterState.SOLID) ( Console.WriteLine ("Premeňte ľad na kvapalinu"); Stav = WaterState.LIQUID; ) else if (State == WaterState.LIQUID) ( Console.WriteLine("Premeňte kvapalinu na paru"); ​​Stav = WaterState.GAS; ) else if (State == WaterState.LIQUID); (State == WaterState.GAS) ( Console.WriteLine("Zvýšenie teploty vodnej pary"); ) ) public void Frost() ( if (State == WaterState.LIQUID) ( Console.WriteLine("Premena kvapaliny na ľad "); Stav = WaterState.SOLID; ) else if (State == WaterState.GAS) ( Console.WriteLine("Premena vodnej pary na kvapalinu"); Stav = WaterState.LIQUID; ) ) )

Voda má tri stavy a v každej metóde sa musíme pozrieť na aktuálny stav, aby sme mohli vykonávať akcie. Výsledkom je, že tri štáty už vedú ku konglomerácii podmienených štruktúr. A aj samotných metód môže byť veľa v triede Voda, kde bude tiež potrebné konať v závislosti od stavu. Preto, aby bol program flexibilnejší, môžeme v tomto prípade použiť vzor State:

Class Program ( static void Main(string args) ( Water water = new Water (new LiquidWaterState()); water.Heat(); water.Frost(); water.Frost(); Console.Read(); ) ) class Voda ( public IWaterState State ( get; set; ) public Water(IWaterState ws) ( State = ws; ) public void Heat() ( State.Heat(this); ) public void Frost() ( State.Frost(this); ) ) rozhranie IWaterState ( void Heat (Voda voda); void Frost (Voda voda); ) trieda SolidWaterState: IWaterState ( verejné void Heat (Voda voda) ( Console.WriteLine("Premeňte ľad na kvapalinu"); voda.Stav = nové LiquidWaterState( ) public void Frost(Voda voda) ( Console.WriteLine("Pokračovať v zmrazovaní ľadu"); ) ) class LiquidWaterState: IWaterState ( public void Heat(Voda voda) ( Console.WriteLine("Premeniť kvapalinu na paru"); ) ; water.State = new GasWaterState( ) public void Frost(Water water) ( Console.WriteLine("Premeniť kvapalinu na ľad"); water.State = new SolidWaterState(); ) ) trieda GasWaterState ( public void); Teplo (vodná voda) ( Console.WriteLine("Zvýšenie teploty vodnej pary"); ) public void Frost(Voda voda) ( Console.WriteLine("Premena vodnej pary na kvapalinu"); voda.Stav = new LiquidWaterState(); ) )

Implementácia vzoru State vám teda umožňuje presunúť správanie, ktoré závisí od aktuálneho stavu objektu, do samostatných tried a vyhnúť sa preťaženiu objektových metód podmienenými konštrukciami ako if..else alebo switch. Okrem toho, ak je to potrebné, môžeme do systému zaviesť nové triedy stavov a použiť existujúce triedy stavov v iných objektoch.

Aby ste správne používali vzory Štát A Stratégia v jadre Java aplikácií je dôležité, aby programátori Java jasne pochopili rozdiel medzi nimi. Hoci obidva vzory, štát aj stratégia, majú podobnú štruktúru a obe sú založené na princípe otvorený/uzavretý, ktorý predstavuje „O“ v princípoch SOLID, sú úplne odlišné v zámery. Vzor Stratégia v Jave používa sa na zapuzdrenie súvisiace sady algoritmov na zabezpečenie flexibility vykonávania pre klienta. Klient si môže zvoliť ľubovoľný algoritmus za behu bez zmeny kontextu triedy, ktorá používa objekt Strategy. Niektoré príklady populárnych vzorov Stratégia je písanie kódu, ktorý používa algoritmy, ako je šifrovanie, kompresia alebo triedenie. Na druhej strane, vzor stavu umožňuje, aby sa objekt v rôznych stavoch správal odlišne. Pretože v reálnom svete má predmet často stavy a v rôznych stavoch sa správa inak, napríklad predajný automat predáva tovar len vtedy, ak je v stave hasCoin, nepredáva sa, kým doň nevhodíte mincu. Teraz môžete jasne vidieť rozdiel medzi stratégiou a vzormi štátu, sú to rôzne zámery. Vzor Stav pomáha spravovať stav objektu, zatiaľ čo vzor Stratégia umožňuje klientovi zvoliť si iné správanie. Ďalším rozdielom, ktorý nie je tak ľahké vidieť, je to, kto vedie zmenu správania. V prípade vzoru Stratégia ide o klienta, ktorý poskytuje kontextu rôzne stratégie v vzore Stav, prechod je riadený kontextom alebo stavom samotného objektu. Okrem toho, ak sami spravujete zmeny stavu v objekte State, musí tam byť odkaz na kontext, napríklad predajný automat musí byť schopný zavolať metódu setState(), aby zmenil aktuálny stav kontextu. Na druhej strane objekt Stratégia nikdy neobsahuje odkaz na kontext, klient sám odovzdáva stratégiu podľa vlastného výberu do kontextu. Rozdiel medzi vzormi State a Strategy je jednou z populárnych otázok na rozhovor o vzoroch Java, v tomto článku o vzoroch Java sa na to pozrieme bližšie. Preskúmame niektoré podobnosti a rozdiely medzi vzormi stratégie a stavu v jazyku Java, ktoré vám pomôžu lepšie pochopiť tieto vzorce.

Podobnosti medzi vzormi štátu a stratégie

Ak sa pozriete na UML diagram vzorov Stav a Stratégia, všimnete si, že oba vyzerajú podobne. Objekt, ktorý používa stav na zmenu svojho správania, je známy ako kontextový objekt, podobne objekt, ktorý používa stratégiu na zmenu svojho správania, sa označuje ako kontextový objekt. Pamätajte, že klient interaguje s objektom Context. V prípade vzoru Stav kontext deleguje metódy volania na objekt Stav, ktorý je držaný ako aktuálny objekt, a v prípade vzoru Stratégia kontext používa objekt Stratégia ako parameter alebo je poskytnutý počas vytvárania. kontextu objektu. UML diagram vzoru stavu v Jave
Tento diagram UML pre vzor State zobrazuje klasický problém vytvárania objektovo orientovaného dizajnu predajných automatov v Jave. Môžete vidieť, že stav predajného automatu je reprezentovaný pomocou rozhrania, ktoré má potom implementáciu reprezentujúcu konkrétny stav. Každý stav má tiež odkazy na kontext objektu na vykonanie prechodu do iného stavu v dôsledku akcií volaných v kontexte.
Tento UML diagram pre vzor stratégie obsahuje funkčné implementácie druhov. Keďže existuje veľa triediacich algoritmov, tento návrhový vzor umožňuje klientovi vybrať si algoritmus pri triedení objektov. v skutočnosti Kolekčný rámec Java používa tento vzor implementáciou metódy Collections.sort(), ktorá sa používa na triedenie objektov v jazyku Java. Jediný rozdiel je v tom, že namiesto toho, aby umožnil klientovi vybrať si triediaci algoritmus, umožňuje mu špecifikovať stratégiu porovnávania odovzdaním inštancie rozhrania Comparator alebo Comparable do Java. Pozrime sa na niekoľko podobností medzi týmito dvoma hlavnými návrhovými vzormi v jazyku Java:
  1. Oba vzory, stav a stratégia, uľahčujú pridávanie nového stavu a stratégie bez ovplyvnenia kontextu objektu, ktorý ich používa.

  2. Oba tieto udržiavajú váš kód podľa princípu otvoreného/uzavretého, čo znamená, že dizajn bude otvorený rozšíreniam, ale nebude možné ho upravovať. V prípade vzorov Stav a Stratégia je kontext objektu uzavretý pred úpravami, zavádzaním nových stavov alebo nových stratégií, prípadne nepotrebujete upravovať kontext iného štátu alebo minimálne zmeny.

  3. Rovnako ako kontext objektu začína stavom inicializácie objektu vo vzore Stav, kontext objektu má tiež predvolenú stratégiu v prípade vzoru Stratégia v Jave.

  4. Vzor Stav predstavuje rôzne správanie vo forme rôznych stavov objektov, zatiaľ čo vzor Stratégia predstavuje odlišné správanie vo forme rôznych stratégií objektov.

  5. Oba modely, stratégia aj stav, závisia od podtried implementácie správania. Každá konkrétna stratégia rozširuje abstraktnú stratégiu; každý stav je podtriedou rozhrania alebo abstraktnej triedy, ktorá sa používa na reprezentáciu stavu.

Rozdiely medzi vzormi stratégie a stavu v Jave

Takže teraz vieme, že vzory štátu a stratégie sú podobné v štruktúre, ale ich zámer je odlišný. Pozrime sa na niektoré kľúčové rozdiely medzi týmito dizajnovými vzormi.
  1. Vzor stratégie zapuzdruje súbor súvisiacich algoritmov a umožňuje klientovi používať zameniteľné správanie napriek zloženiu a delegovaniu za behu, na druhej strane vzor Stav pomáha triede prejavovať rôzne správanie v rôznych stavoch.

  2. Ďalší rozdiel medzi vzormi štátu a stratégie je v tom, že štát zapuzdruje stav objektu, zatiaľ čo vzor stratégie zapuzdruje algoritmus alebo stratégiu. Keďže stav je spojený s objektom, nemožno ho znova použiť, ale oddelením stratégie alebo algoritmu od jeho kontextu ho môžeme znova použiť.

  3. Vo vzore štátu môže osobný stav obsahovať odkaz na kontext na implementáciu prechodov medzi štátmi, ale stratégia neobsahuje odkaz na kontext, v ktorom sa používa.

  4. Implementácia stratégie môže byť odovzdaná ako parameter objektu, ktorý ju bude používať, napríklad Collection.sort() berie komparátor, čo je stratégia. Na druhej strane, stav je súčasťou samotného kontextu objektu a v priebehu času kontext objektu prechádza z jedného stavu do druhého.

  5. Hoci stratégia aj štát sa riadia princípom otvorený/uzavretý, stratégia sa riadi aj princípom jednotnej zodpovednosti, pretože každá stratégia obsahuje individuálny algoritmus, pričom rôzne stratégie sú od seba nezávislé. Zmena jednej stratégie si nevyžaduje zmenu inej stratégie.

  6. Ďalší teoretický rozdiel medzi vzormi Stratégia a Stav je v tom, že tvorca definuje časť objektu „Ako“, napr. „Ako“ triediaci objekt triedi dáta, na druhej strane vzor Stav definuje „čo“ a „kedy“. časti objektu, napr. čo dokáže objekt, keď je v určitom stave.

  7. Poradie stavových prechodov je dobre definované vo vzore štátu, neexistuje takáto požiadavka na vzor stratégie. Klient si môže slobodne zvoliť akúkoľvek implementáciu Stratégie podľa vlastného výberu.

  8. Niektoré z bežných príkladov vzoru stratégie sú zapuzdrenie algoritmov, ako sú triediace algoritmy, šifrovacie algoritmy alebo kompresný algoritmus. Ak vidíte, že váš kód musí používať rôzne druhy súvisiacich algoritmov, mali by ste zvážiť použitie vzoru stratégie. Na druhej strane, rozpoznanie použitia vzoru Stav je celkom jednoduché, ak potrebujete spravovať prechody stavov a stavov bez veľkého množstva vnorených podmienených príkazov, vzor Stav je tým správnym vzorom.

  9. Posledným, ale jedným z najdôležitejších rozdielov medzi vzormi Stav a Stratégia je, že zmenu Stratégie vykonáva Klient, zatiaľ čo zmenu Stavu môže vykonať kontext alebo samotný stav objektu.

Je to všetko o rozdiel medzi štátnymi a strategickými vzormi v Jave. Ako som povedal, obe vyzerajú podobne vo svojich triedach a UML diagramoch, obe poskytujú otvorené/uzavreté princípy a zapuzdrujú správanie. Použite vzor stratégie na zapuzdrenie algoritmu alebo stratégie, ktorá je vystavená kontextu za behu, napríklad ako parameter alebo zložený objekt, a použite vzor State na riadenie prechodov stavov v jazyku Java. Originál