/ janturon.cz / Od kodéra k analytikovi / Chain of Responsibility a State

Chain of Responsibility a State

Chain of Responsibility

Každý objekt by měl mít jednu jednoduchou zodpovědnost. Pokud je tato řešena složitým větvením, je vhodné ji rozdělit na několik menších zodpovědností, které si zpracování úkolu předávají (pokud si s ním neporadí), nebo řetěz volání zastaví.

Následující příklad z důvodu přehlednosti nedodžuje zásadu zapouzdření. Ta může být snadno dodána pomocí Vlastností.

Příklad: v počítačové hře může hráč dostat zásah, který mu sebere nějaké životy. Pokud hráč může mít štíty, stává se metoda ošetřující zásah příliš složitá. Rozdělme ji na dvě jednodušší zodpovědnosti: HitShields a HitLife, volané stejně pomocí rozhraní Responsibility:

struct Player; struct Responsibility { virtual bool Hit(Player* p, int& power)=0; }; struct HitLife : Responsibility { bool Hit(Player*, int& power); }; struct HitShields : Responsibility { bool Hit(Player*, int& power); };

Atribut power určující sílu zásahu je zde předáván odkazem, protože zásah do štítů může zmírnit přímý zásah. Vzor Chain of Responsibility zavolá metodu Hit z HitShields, a pokud zásah zničil štíty (nebo hráč žádné nemá), dostane přímý zásah pomocí HitLife. Toto zřetězení (obecně i pro větší počet předání než dvě) lze zapsat jako:

struct Chain : Responsibility { Responsibility *Action; Chain *Next; Chain(Responsibility* action) : Action(action) { } bool Hit(Player*, int& power); }; bool Chain::Hit(Player* p, int& power) { if(!Action->Hit(p,power)) return false; if(Next!=NULL) return Next->Hit(p,power); }

A do třídy řešící hráče toto zřetězení zakomponujeme do konstruktoru:

class Player { Chain *ch; public: string name; int shields, life; Player(string name) : name(name),life(10), shields(0) { ch = new Chain(new HitShields()); ch->Next = new Chain(new HitLife()); } void Armor(int bonus) { shields+= bonus; cout << name << "'s shields are now " << shields << endl; } void Harm(int power) { cout << name << " is is hit with power " << power << endl; ch->Hit(this,power); } };

Kde metoda Armor() přidává hráči štíty a Harm zpracovává zásah (neimplementuje metodu Hit z Responsibility). Metodu Hit z rozhraní Responsibility můžeme pak pro HitShields a HitLife implemetovat podle výše zmíněného slovního popisu:

bool HitLife::Hit(Player* p, int& power) { if(p->life<=power) { p->life = 0; cout << p->name << " is dead" << endl; return false; } p->life-= power; cout << p->name << "'s life is now " << p->life << endl; return false; } bool HitShields::Hit(Player* p, int& power) { if(p->shields==0) return true; if(p->shields>power) { p->shields-= power; cout << p->name << "'s shields are now " << p->shields << endl; return false; } power-= p->shields; p->shields = 0; cout << p->name << "'s shields failed!" << endl; return true; }

Aplikace je teď snadno upravitelná i rozšiřitelná: HitShields a HitLife řeší zásah, způsob jejich volání řeší Chain. Tuto funkčnost lze snadno používat, viz ukázku:

int main() { Player *p = new Player("Pirate"); p->Armor(4); p->Harm(1); p->Harm(5); p->Harm(6); p->Harm(3); } /* výstup */ Pirate's shields are now 4 Pirate is hit with power 1 Pirate's shields are now 3 Pirate is hit with power 5 Pirate's shields failed! Pirate's life is now 8 // 10-(5-3) Pirate is hit with power 6 Pirate's life is now 2 Pirate is hit with power 3 Pirate is dead

State

Stav objektu znamená konstelaci jeho atributů. Ty obvykle nemohou být všech možných kombinací, ale pouze těch konzistentních a smysluplných, jichž bývá malé množství.

Pokud je přechod z jednoho stavu do druhého implementačně náročný, je vhodné založit více objektů, každý z nich reprezentující určitý (konzistentní smysluplný) stav.

Mějme třídu reprezentující ukazatel života hráče v nějaké hře:

class LifeBar { string info; public: static int life; LifeBar(string info) : info(info) { } void Show() { if(life<0) life = 0; if(life>100) life = 100; cout << info << " " << life << '%' << endl; } }; int LifeBar::life = 100;

Ten bude nabývat barevných stavů zelené, žluté a červené, podle míry poškození. Přepínání mezi stavy byde řešit jiná třída:

class State { LifeBar green, yellow, red, black; // stavy LifeBar* state; // aktuální stav public: State() : green("Green"), yellow("Yellow"), red("Red"), black("Black") { state = &green; } void Change(int delta) { // volba stavu LifeBar::life+= delta; if(LifeBar::life>60) state = &green; else if(LifeBar::life>25) state = &yellow; else if(LifeBar::life>0) state = &red; else state = &black; state->Show(); } };

V programu se pak stav mění automaticky podle poškození:

int main() { State health; health.Change(-50); health.Change(+25); health.Change(-50); health.Change(+25); health.Change(-30); return 0; } /* výstup */ Yellow 50% Green 75% Red 25% Yellow 50% Red 20%