UPOZORNĚNÍ

Zkoušky OCUP Fundamental a OCUP Intermediate již není možné absolvovat. Nově jsou k dispozici OCUP 2 Foundation a OCUP 2 Intermediate. Texty uvedené na těchto neodpovídají plně novým zkouškám. Aktuální text najdete na nových stránkách.

Testy znalostí UML

Chcete si kdykoliv před, při nebo po čtení těchto stránek udělat test znalostí UML? Máte možnost absolvovat takový, který připravil autor těchto stránek. Vše podstatné najdete na http://www.kurzy-uml.cz.

Stavové diagramy (States Machines)

Stavové automaty se používají na modelování stavů objektů reagující na události. Jsou obsaženy v 15 % testových otázek, které se týkají balíku BehaviorStateMachines (bez redefinicí a časové události).

Metamodel: Package Dependencies
Metamodel: State Machines

Třída StateMachine

Stavový automat (třída StateMachine) je specializací třídy BasicBehaviors::Behavior. Chování stavového automatu se modeluje pomocí uzlů (tzv. stavů a pseudostavů), které jsou propojeny orientovanými hranami (přechody). Přechod je typicky vyvolán nějakou událostí. Během přechodů může stavový automat spouštět chování asociované s různými elementy daného automatu.

Stavový automat se skládá z alespoň jednoho regionu, který obsahuje již zmíněné uzly (výchozí třída Vertex) a přechody (třída Transition).

Objekt, který poskytuje stavovému automatu svůj kontext, reaguje na vnější události. Jeho životní cyklus je modelován jako řada stavů a událostí. Chování tohoto objektu je dáno předchozím chováním (a tedy stavem). Těmito objekty mohou být třídy, případy užití či celé systémy. Pokud tento objekt poskytuje kontext více stavovým automatům, pak tyto stavové automaty musí být vzájeně v konzistentním stavu.

Stavový automat má úložiště událostí (event pool) do kterého připlouvají postupně jedna událost za druhou. Stavový automat vždy zpracovává v čase maximálně jednu událost. Ostatní čekají, až na ně přijde řada. Pořadí zpracování událostí však není nikterak definováno. Ve standardu je tento postup nazýván zpracování run-to-completion.

Na stavový automat jakožto nepřímou specializaci třídy Kernel::Classifier lze použít generalizaci (třída Kernel::Generalization). Specializace stavového automatu je rozšířením (extension) toho původního. V rozšířeném stavovém automatu lze přidávat uzly a přechody, redefinovat původní, měnit regiony, z jednoduchých stavů dělat složené a další.

Notace

Diagram stavového automatu je graf představující tento stavový automat. Uzly (stavy a pseudostavy) jsou zobrazovány dle své definice (viz odpovídající třídy dále v této kapitole), přechody obecně pomocí šipek. Zobrazení asociace mezi stavovým automatem a jeho kontextem není standardem definováno.

Příklad stavového automatu

Stavový automat jakožto nepřímá specializace třídy Kernel::Classifier je možné zobrazit i jako klasifikátor, přičemž se použije klíčové slovo «statemachine». Jiná notace není standardem určená (některé nástroje používají obdélník se zakulacenými rohy a názvem stavového automatu v horní části).

Stavy získané pomocí generalizace se kreslí v rozšířeném automatu přerušovanou nebo šedou linkou.

Třída Vertex

Třída Vertex je abstraktním předkem všech uzlů (tj. stavů a pseudostavů) ve stavovém diagramu. V tomto uzlu mohou začínat (odchozí) nebo končit (příchozí) hrany.

Třída State, část první

Stav objektu (třída State) je dán situací, ve které se daný objekt nachází. Tato situace může být statická, kdy objekt na něco čeká (typicky na událost zvenčí), nebo dynamická, kdy objekt něco vykonává. Uvedenou situací může být i kombinace hodnot atributů objektu nebo relace s dalšími objekty.

Objekty si navzájem předávají události, které mohou vést ke změně stavu.

Stav může být aktivní nebo neaktivní. Aktivním se stává ve chvíli, kdy je do něj vstoupeno pomocí přechodu. Neaktivním se stává ve chvíli, kdy je pomocí přechodu opuštěn.

Kdykoliv je do stavu vstoupeno, spustí se nejprve jeho vstupní chování – viz asociace s třídou Behavior. Podobně před opuštěním stavu je spuštěno jeho výstupní chování.

Stav taktéž může mít definované chování, které se vykonává během toho, kdy je stavový automat v tomto stavu (tzv. do activity, opět viz jedna z asociací s třídou Behavior; česky používám průběžné chování). Toto chování je spuštěno po dokončení vstupního chování. Pokud průběžné chování je dokončeno před opuštěním stavu, pak vygeneruje událost dokončení (completion event). Pokud je stav opouštěn v době, kdy je průběžné chování stále vykonáváno, je toto předčasně ukončeno.

Všechna uvedená chování mohou používat atributy a asociační konce vlastnící entitou.

Stav může (ale nemusí) mít název. Pokud jej nemá, jedná se o anonymní stav. Všechny takové stavy jsou jedinečné.

Stav se (obecně) zobrazuje v obdélníku se zakulacenými rohy a názvem uvnitř. Alternativou je zobrazení názvu v záložce nad obdélníkem.

Notace stavu

Stav může být rozdělen do několika oddílů oddělených linkou. Tyto oddíly jsou:

  • Název stavu – zobrazuje název stavu, existuje-li. Pokud se jedná o stav podautomatu (viz dále), pak se zde zobrazuje jeho název za dvojtečkou a názvem stavu.
  • Vnitřní aktivity – v tomto oddíle je zobrazen seznam interních akcí a (průběžného) chování, které je v tomto stavu spouštěno. Před aktivitami je uvedeno, za jakých okolností jsou spouštěny. Existuje několik rezervovaných slov, které nelze použít v jiném významu:
    • entry – identifikuje chování, které je spuštěno při vstupu do stavu.
    • exit – identifikuje chování, které je spuštěno při výstupu ze stavu (jak ale bylo uvedeno výše, nemusí být spuštěno vždy).
    • do – identifikuje průběžné chování.
  • Vnitřní přechody – obsahuje seznam vnitřních přechodů, notace je dána třídou Trigger (viz dále).
  • Dekompozice – oddíl pro dekompozici složeného stavu (viz dále).
Notace stavu s akcemi

Poznámka ke standardu: S vnitřními aktivitami je standard poněkud nekonzistentní. Podle definice může mít stav volitelně jedno vstupní chování, jedno výstupní a jedno průběžné. Toho se drží v popisu až do uvedení notace. Tam, v části věnované oddílu pro vnitřní aktivity, však již bez problémů uvádí bez dalšího vysvětlení v příkladu více vstupních aktivit. Otázkou také je, co je danou aktivitou míněno – zda třída Activity nebo něco jiného. V příkladu výše jsem to použil taktéž. Jeden možný výklad je, že se jedná o seznam akcí aktivity, bohužel však zůstává otázka, v jakém pořadí mají být uváděny.

Dalším ne zcela jasným bodem je, co je míněno okolností jinou než exit/entry/do. Onou okolností může být událost, a pak bychom mohli místo o vnitřní aktivitě klidně mluvit o vnitřním přechodu.

Třída Transition

Přechod mezi stavy (třída Transition) říká, za jakých podmínek je možné přejít z jednoho stavu do druhého. Jedná se o jednosměrný vztah mezi zdrojovým a cílovým uzlem. Přechod má tři volitelné atributy (v metamodelu definované asociacemi):
  • trigger: Trigger [0..*] – určuje události, které mohou (tedy nemusí!) vynutit přechod ze zdrojového do cílového uzlu.
  • guard: Constraint [0..1] – pokud dojde k události, která může způsobit změnu stavu pomocí tohoto přechodu, je vyhodnocena tato podmínka. Vyhodnocení by mělo být bez vedlejších efektů (tedy prostý výraz). Pokud je výsledek vyhodnocení v pořádku, přechod do dalšího stavu může být použit.
  • effect: Behavior [0..1] – Volitelné chování (efekt), které je spuštěno, pokud k tomuto přechodu opravdu dojde.

Tvůrce stavového automatu by měl zajistit, aby všechny přechody z jedné akce byly navzájem výlučné.

Výchozí notace přechodu se zapisuje následujícím způsobem:

[trigger1[, trigger2[, …]]] [‘[‘guard’]’] [‘/’ effect]

Tato notace se používá jak pro přechody mezi uzly, tak i pro vnitřní přechody stavů (viz dále).

Notace přechodů

Z kapitoly věnované základům chování víme, že existují čtyři typy událostí definované v balíku Communication:
  • událost volání (třída CallEvent),
  • přijmutí a odeslání signálu (třída SignalEvent),
  • časová událost (třída TimeEvent),
  • a událost změny (třída ChangeEvent).
Každá tato událost může vyvolat přechod. Tyto události a akce přechodu mohou být zobrazovány jednak textově a jednak připojením grafických symbolů k přechodu. Tato sekvence symbolů stále znamená týž přechod.

Za sekvencí symbolů pro události může následovat sekvence symbolů pro akce. Tato sekvence akcí je vlastně vizualizace akcí v chování, které odpovídá efektu spouštěného při přechodu. Akce se zobrazují v obdélníku, ve kterém je textová reprezentace této akce. Alternativně lze použít notaci definovanou pro daný typ akce.

Událost volání

Událost volání vznikne tehdy, dojde-li k vyvolání konkrétní operace třídy poskytující kontext pro stavový automat. Událost volání má stejný název jako název volané operace a dále obsahuje odpovídající parametry.

Následující příklad ukazuje dva stavy souboru v textovém editoru. Soubor může být buďto uložený nebo změněný, kdy změny nejsou dosud trvale uloženy na disku. Ke změnám stavu dochází na základě událostí zavolání odpovídajícího chování. Je vhodné, aby na přechodech bylo vidět nejen název operace, ale celá její signatura (tedy včetně typů parametrů).

Příklad události volání

Událost přijmutí a odeslání signálu

Příjem signálu se značí jako pětiúhelník (viz příklad) a značí událost vyvolávající přechod. Textová notace je pak zobrazena uvnitř symbolu. Pokud událost obsahuje podmínku (guard), je taktéž zobrazena v uvedeném pětiúhelníku:

<trigger> [‘,’ <trigger>]* [‘[‘ <guard> ‘]’]

V případě více událostí na přechodu je příjem signálu vždy první a může se tu vyskytnout pouze jednou. Součástí signálu mohou být i parametry.

Notace přijmutí signálu

Odeslání signálu se opět značí pětiúhelníkem, ale trochu jiného tvaru (viz obrázek). Notace je dána třídou SendSignalAction. Parametry signálu se zobrazují uvnitř daného tvaru.

Na cestě symbolů přechodů musí být odeslání signálu uvedeno za příjmem, pokud je tento uveden. Je možné odesílat více signálů.

Notace odeslání signálu

Příklad použití přijmutí a odeslání signálu: Mějme firmu, kde budeme měsíčně sledovat útratu našich zákazníků (může to být třeba mobilní operátor). Pokud útrata za poslední 3 měsíce přesáhne částku 1500 Kč, pak informujeme obchodníka, který zákazníkovi může zavolat a nabídnout mu nějakou slevu nebo další službu za zvýhodněnou sazbu. Stavový automat typu zákazníka reaguje na příjem události od billingového systému. Ve zprávě dostaneme informaci jednak o platbě za aktuální zúčtovací období a pak i informaci o celkové platbě za sledované období. Stavový automat je dokončen okamžikem výpovědi smlouvy o poskytování služeb. Poznámka: V diagramu ještě používám pseudostavy pro rozhodování a sbíhání, které budou vysvětleny záhy.

Příklad přijmutí a odeslání signálu

Časová událost

Časová událost se značí v závislosti na tom, zda chceme určit konkrétní čas nebo nějaký časový úsek. Pro konkrétní čas použijeme klíčové slovo at a zápis času, např.:

at (11:00)

at (Jan 1, 2000, Noon)

Zde není standard nijak striktní, použití závorek je možné pro lepší čitelnost (totéž platí i pro časový úsek).

Časový úsek se zapíše klíčovým slovem after a časem, který musí uběhnout:

after (10 seconds)

after (1 day)

Příklad použití: Pokud máme předplacenou kartu mobilního operátora, pak ta na svého majitele čeká v obchodě v nějakém předkupním stavu (např. Připravená pro první zavolání). Jakmile si ji zákazník zakoupí a provede první hovor, karta bude ve stavu aktivní. V tomto stavu bude tak dlouho, dokud zákazník bude volat. Pokud ale 6 měsíců neprovede žádný hovor, karta se deaktivuje. Pak má zákazník ještě dva měsíce na to, aby to změnil. Pokud nepodnikne žádný krok, předplacená karta zaniká.

Příklad časových událostí

Další příklad: Následující situace demonstruje jednoduchý semafor a způsob přepínání světel. Použitý pseudostav pro rozhodování zatím berte intuitivně, za chvíli bude vysvětlen řádně.

Příklad časových událostí

Následující obrázek ukazuje totéž, jen s jiným zápisem pro akci v rámci přechodu.

Příklad časových událostí

Událost změny

Událost změny reaguje na změnu definované podmínky. Jakmile vyhodnocení podmínky se změní z true na false nebo opačně, je tato změna vyvolána. Značí se klíčovým slovem when následovaný logickým výrazem.

Jako příklad rozšiřme výše uvedenou předplacenou kartu. Řekněme, že budeme chtít v případě, že zákazníkovi klesl kredit pod 100 Kč, dát mu o tom vědět např. pomocí SMS. Stav Active by potom mohl vypadat následovně:

Příklad událost změny

Je nutné zde říct, že upozornění o výši kreditu nebude chodit neustále, pokud bude tento menší než 100 Kč, ale odejde pouze v okamžiku, kdy se hodnota výrazu za when změní z false na true. Jinými slovy další SMS dostane zákazník až tehdy, kdy kredit vzroste nad 100 Kč a pak znovu pod tuto hranici klesne. Nestane se tedy to, že by byl zákazník zahlcen SMS (pokud tedy nebude neustále kolísat s kreditem kolem sta korun, ale to lze vyřešit např. dobitím minimální částky např. 200 Kč).

Vnitřní a rekursivní přechod

Vnitřní přechod (internal transition) je přechod, ke kterému dojde v rámci stavu, aniž by byl tento stav opuštěn a znovu do něj vstoupeno. Znamená to tedy, že nedojde k vyvolání vstupního a výstupního chování. Rekursivní přechod (pozor, tento pojem není ve standardu zmíněn) je přechod, kdy vstupní i výstupní stav je tentýž. V tomto případě ovšem k volání vstupního a výstupního chování dochází.

Příklad vnitřního a rekursivního přechodu

Třída Region

Region (třída Region) je součástí stavového automatu nebo složeného stavu. Obsahuje stavy (třída Vertex) a přechody. Sémantika se liší dle toho, zda je region součástí stavového automatu (ten má vždy alespoň jeden region) nebo složeného stavu (bude vysvětleno dále).

Třída Pseudostate, část první

Pseudostav (třída Pseudostate) je používán při zobrazování složitějších přechodů mezi stavy. UML definuje několik různých pseudostavů (výčet PseudostateKind), přičemž každý má svou vlastní sémantiku.

Počáteční pseudostav

S počátečním pseudostavem (kind = initial) jste se již setkali ve většině dosud použitých příkladů. Pro tento pseudostav platí několik pravidel:
  • Počáteční pseudostav může mít pouze jeden odchozí přechod, žádný příchozí.
  • Při vytvoření instance stavového automatu se přechodem automaticky přejde z počátečního pseudostavu do tzv. výchozího stavu.
  • Region může mít maximálně jeden počáteční pseudostav (pokud tedy máme jednoduchý stavový automat s jedním regionem – což bylo ve všech dosavadních příkladech – nelze použít více než jeden počáteční pseudostav).
  • Přechod vedoucí z počátečního pseudostavu může mít přiřazené chování, ale žádnou událost (trigger) nebo podmínku.

Počáteční pseudostav se zakresluje černým kolečkem. V regionu stavového automatu pro classifierBehavior může přechod z počátečního pseudostavu být označen událostí, která vytváří objekt, jinak musí být přechod bez popisky.

Rozhodovací pseudostav

Pokud se při přechodu narazí na rozhodovací pseudostav (kind = choice), dojde k vyhodnocení podmínek odchozích přechodů. Pokud se stane, že by byla splněna podmínka pro více odchozích hran, bude vybrána náhodná z nich. Pokud by nebyla splněna ani jedna podmínka, pak nejde o správně formulovaný stavový automat.

Při modelování je možné použít v jedné odchozí hraně předdefinovanou podmínku else. Ta zaručí, že pokud není splněna žádná podmínka, pak se pokračuje po tomto přechodu.

Rozhodovací pseudostav musí mít alespoň jeden příchozí a alespoň jeden odchozí přechod.

Rozhodovací pseudostav se značí kosočtvercem.



Sbíhací pseudostav

Sbíhací pseudostav (kind = junction) má poměrně volnou sémantiku. Používá se pro sdružení více přechodů. Např. může více přechodů spojit v jeden (operace spojení, merge). Stejně tak může být použit pro rozdělení přechodu do více na základě (ideálně výlučných) podmínek na odchozích přechodech. Pro maximálně jeden odchozí přechod je možné použít i předdefinovanou podmínku else. Sbíhací pseudostav musí mít alespoň jeden příchozí a alespoň jeden odchozí přechod.

Rozdíl mezi rozhodovacím a sbíhacím pseudostavem v případě větvení přechodů je ten, že rozhodovací pseudostav využívá dynamické podmíněné větvení, zatímco sbíhací pseudostav statické podmíněné větvení. Vysvětlení těchto dvou postupů však je mimo rámec tohoto textu (a potažmo standardu UML také).

Sbíhací pseudostav se zakresluje černým kolečkem (stejně jako počáteční pseudostav, což může být matoucí, ale pouze do té doby, než si uvědomíte, že počáteční pseudostav nesmí mít příchozí přechod). V některých nástrojích se proto zobrazuje menší než počáteční pseudostav.



Další pseudostavy

Pseudostavy přerušení, vstupu a výstupu budou popsány v pokračování popisu třídy State. Stavy mělké a hluboké historie pak v pokračování této kapitoly dále.

Třída FinalState

Konečný stav (třída FinalState) není, jak by se mohlo zdát, pseudostavem, ale poctivým stavem, který říká, že region, kterému náleží, je dokončen. Je-li tento region přímo součástí stavového automatu a všechny ostatní regiony dosáhly konečného stavu, pak to znamená, že i stavový automat je dokončen.

Konečný stav nesmí mít žádné odchozí přechody, stejně tak nesmí mít žádné regiony. Dále finální region nemá podautomaty, ani vstupní, průběžné či výstupní chování.

Konečný stav se zakresluje kolečkem s černým bodem vprostřed.

Třída State, část druhá

UML rozlišuje tři druhy stavů:
  • jednoduché stavy (simple states),
  • složené stavy (composite states),
  • a stavy podautomatu (submachine states).

Složený stav dále ještě dělíme na jednoduché složené stavy (simple composite state) a ortogonální složené stavy (orthogonal composite state).

Jednoduché stavy byly ty, co jsme dosud probírali.

Složený stav

Složený (nebo také kompozitní) stav je stav, který obsahuje jeden region nebo je rozložen (dekomponován) do dvou či více regionů. Každý takový region vlastní množinu uzlů a přechodů, jejíž prvky nelze kombinovat s prvky jiného regionu. Každý stav v takovém regionu se nazývá podstav (substate). Podstav může opět být složeným stavem. Regiony jsou ve složeném stavu odděleny přerušovanou linkou.

Podle počtu regionů mluvíme o jednoduchém složeném stavu v případě jednoho regionu a o ortogonálním složeném stavu v případě dvou a více regionů.

Příklad složeného stavu

Každý region složeného stavu může mít počáteční pseudostav a konečný stav. Přechod do složeného stavu pak znamená přechod na všechny počáteční pseudostavy všech regionů složeného stavu. Přechod do konečného stavu v regionu znamená pouze konec chování v daném regionu. Teprve až konec chování ve všech regionech složeného stavu znamená i konec chování složeného stavu a vyvolá událost jeho dokončení (event completion, viz úvod). Toto chování lze ovlivnit použitím pseudostavu přerušení (Pseudostate.kind = terminate), které se značí dvěma přeškrtnutými linkami. Při vstupu do tohoto pseudostavu se vynutí přerušení běhu chování celého složeného stavu. Nejsou ani vykonány žádné výstupní chování kromě těch, které jsou asociovány s přechodem do tohoto pseudostavu. Vstup do tohoto pseudostavu odpovídá zavolání akce DestroyObjectAction.

Příklad složeného stavu s pseudostavem terminate

Pro vstup do složeného stavu nebo stavového automatu lze použít vstupní pseudostav (Pseudostate.Kind = entryPoint). Vede do něj přechod z jiného stavu a pokračuje přechodem do (pod)stavu. Vstupní pseudostav se značí prázdným kolečkem.

Pododobně je možné pro stavový automat či složený stav použít výstupní pseudostav (Pseudostate.Kind = exitPoint), který spojuje vnitřní přechod ve složeném stavu s přechodem vedoucím ze složeného stavu. Vstoupením do výstupního pseudostavu v libovolném regionu složeného stavu odpovídá ukončení tohoto složeného stavu a vyvolání přechodu vedoucího z tohoto výstupního pseudostavu.

Výstupní pseudostav se zachycuje prázdným přeškrtnutým kolečkem. Umístěn může být jak v rámci diagramu stavového automatu nebo složeného stavu, tak na hranici takového diagramu či stavu.

Příklad složeného stavu s pseudostavy entryPoint a exitPoint

Pokud podstav nemá počáteční pseudostav, pak lze k takovému podstavu přistupovat dvěma způsoby: buďto jako ke špatně definovanému podstavu, anebo můžeme vzít jako první podstav takový, který nemá žádnou vstupní hranu. O podobné situaci s koncovým podstavem standard nehovoří (logicky bychom to ale mohli brát obdobně).

Příklad jednoduchého složeného stavu:

Příklad navazování spojení

Pokud chce v diagramu zakrýt dekompozici stavu (a tu uvést v jiném diagramu), pak v pravém dolním rohu uvedeme značku dekompozice:

Složený stav s ukrytou dekompozicí

Stavy podautomatu

Stavový automat lze chápat i jako samostatnou část, kterou lze využívat v jiném stavovém automatu (představit si to můžete jako volání podprocesu). Mějme např. stavový automat pro přihlášení do systému:



To lze pak použít např. tímto způsobem:



Třída Pseudostate, část druhá

Spojení a rozdělení

Pseudostav rozdělení (kind = fork) rozděluje přicházející přechod na dva či více přechodů, přičemž každý musí končit v různých regionech (např. v regionech kompozitního stavu). Části přechodu vycházející z pseudostavu rozdělení nesmí mít podmínky ani události. Příchozí hrana může být právě jedna, odchozích hran minimálně dvě.

Pseudostav spojení (kind = join) spojuje přechody vycházející ze zdrojových uzlů v různých regionech. Přechody vcházející do pseudostavu spojení nesmí mít přiřazenou podmínku ani událost (trigger). Příchozí přechody musí být minimálně dva, počet odchozích právě jeden.

Oba pseudostavy se zobrazují silnou černou čarou (vodorovnou nebo svislou).



Mělká a hluboká historie

To, že přejdeme z jednoho stavu do druhého, ještě neznamená, že se k tomu původnímu nebudeme někdy chtít vrátit a to nejlépe do takové situace, ve které jsme jej opustili. Příkladem může být nakupování na internetu. Tam se můžeme pohybovat mezi zbožím, seznamem dostupných položek a nákupním košíkem. Je dost otravné, pokud jste v nějaké kategorii zboží, podíváte se na svůj košík a pak se složitě zase dostáváte do původní kategorie. K uživatelskému komfortu slouží pseudostav mělká (kind = shallowHistory) a hluboká (kind = deepHistory) historie.

Na následujícím diagramu se zkraje dostaneme do stavu Procházení nabídky. Při prvním vstupu se podíváme na zobrazení seznamu. Pokud se pak ale podíváme na košík a následně se vrátíme zpět do procházení nabídky, přejdeme hned do stavu, ve kterém jsme tento opouštěli. Protože jsme použili mělkou historii, bude si tento pseudostav pamatovat pouze stav na stejné úrovni. Pokud si chceme zapamatovat historii do libovolné úrovně, použijeme pseudostav hluboká historie. Pak např. budeme vědět nejen to, že si zákazních procházel seznam, ale i to, jak byl seřazen.

Pseudostav se zobrazuje v kolečku s písmenkem H. V případě hluboké historie je navíc u písmenka hvězdička.

Příklad mělké historie

Příklad hluboké historie

Poznámka ke knize [ocup-cg]: Ta oproti požadavkům na znalosti k certifikaci úrovně Intermediate navíc zmiňuje redefinování a protokolové stavové automaty. Na ně se však ptají až na úrovni Advanced.

Žádné komentáře:

Okomentovat

Líbila se vám právě přečtená kapitola?

Líbil se vám článek? Přinesl vám užitek? Pokud ano, můžete mi zaslat pár drobných, čímž jednak dáte najevo, že se vám tu opravdu líbilo, a jednak mi ukážete, že má práce není zbytečná. Informace o darovací platbě zde.