/ janturon.cz / Od kodéra k analytikovi / Most a Fasáda

Most a Fasáda

Most (Bridge)

Most slouží v klientském kódu k přepnutí systému nebo přechodu na jiný systém.


"Systémem" je v této kapitole myšlena část aplikace: jedna či více tříd řešící v aplikaci nějaký ohraničený úkol. Vysvětlující příklad:

// C++ // systém: Class1.h class Class1 { public: static void Info() { cout << "Hello, world!"; } }; // klientský kód #include "Class1.h" int main() { Class1::Info(); return 0; } // Javascript // systém: Class1.js var Class1 = { Info: function() { alert("Hello, world!"); } } // klientský kód <script src="Class1.js"></script> <script> Class1.Info(); </script>
  1. Naprogramujeme proxy, přes kterou budeme volat v klientském kódu stávající systém.
// C++ // proxy: Proxy.h #include "Class1.h" class Proxy { public: static void NewInfo() { Class1::Info(); } }; klientský kód #include "Proxy.h" int main() { Class1.Info(); Proxy::NewInfo(); return 0; } // Javascript // proxy: Proxy.js var Proxy = { NewInfo: function() { Class1.Info(); } } // klientský kód <script src="Class1.js"></script> <script src="Proxy.js"></script> <script> Class1.Info(); Proxy.NewInfo(); </script>
  1. Vyvineme kód, který nahradí požadovanou část systému.
C++ // nový systém: Class2.h class Class2 { public: static void Info1() { cout << "Hello "; } static void Info2() { cout << "world!"; } }; // Javascript // nový systém: Class2.js var Class2 = { Info1: function() { alert("Hello "); }, Info2: function() { alert("world"); } }
  1. Tento kód agregujeme do proxy. Přes proxy postupně vypínáme či přepínáme původní systém a zapínáme nový systém.
// C++ // proxy #include "Class1.h" #include "Class2.h" #define MODE 1 class Proxy { public: static void NewInfo() { if(MODE==1) { Class1::Info(); } else { Class2::Info1(); Class2::Info2(); } } }; // Javascript // proxy var Proxy = { MODE: 1, NewInfo: function() { if(Proxy.MODE==1) { Class1.Info(); } else { Class2.Info1(); Class2.Info2(); } } }

Zde je přepínač makro MODE, může jím ale být i událost v aplikaci. Následující nepovinný bod se týká odstranění původního systému, pokud už nebude přepínán:

  1. Jakmile se starý systém nepožívá, je možné ho z projektu snadno odstranit.
C++ // proxy #include "Class2.h" class Proxy { public: static void NewInfo() { Class2::Info1(); Class2::Info2(); } }; // klientský kód #include Class2.h" int main() { Proxy::NewInfo(); Class2::Info1(); Class2::Info2(); return 0; } // Javascript // proxy var Proxy = { NewInfo: function() { Class2.Info1(); Class2.Info2(); } } // klientský kód <script src="Class2.js"></script> <script> Proxy.NewInfo(); Class2.Info1(); Class2.Info2(); </script>

Kód by byl funkční i s Mostem. Ten však po dokončení změn přináší nadbytečnou složitost, která by se při dalším vývoji vymstila.

Uvedený příklad by bylo možné hravě změnit přímo, i bez Mostu. V praxi se ale pracuje s celými adresáři kódu, některé úpravy trvají i týdny a systém musí být v každém okamžiku funkční a odolný proti zanesení chyby. Most umožňuje provést ekvivalentní úpravu klientského kódu (nehrozí chyba) a všechny změny provádět na jednom místě (proxy). Typickým úkolem na Most v Javascriptu může být zadání v už hotovém webovém projektu: Přejdeme z jQuery na MooTools.

Fasáda

Fasáda znamená vytvoření rozhraní k celému systému, které pak implementujeme jiným způsobem.


Mějme třídy pro ovládání nějaké plošinovky:

// C++ class Player { public: void moveLeft() { ... } void moveRight() { ... } void Reset() { ... } ... }; class Game { public: void Start() { ... } void End() { ... } void Reset() { ... } ... }; // Javascript function Player() { this.moveLeft = function() { ... } this.moveRight = function() { ... } this.Reset = function() { ... } ... } function Game() { this.Start = function() { ... } this.End = function() { ... } this.Reset = function() { ... } ... }

Pokud chceme vytvořit jinou plošinovku s týmiž herními principy, vytvoříme pro ni fasádu

// C++ struct IArcade { virtual void moveLeft()=0; virtual void moveRight()=0; virtual void PlayerReset()=0; virtual void Start()=0; virtual void End()=0; virtual void GameReset()=0; }; // Javascript function IArcade() { this.moveLeft = function() { throw "not implemented"; } this.moveRight = function() { throw "not implemented"; } this.PlayerReset = function() { throw "not implemented"; } this.Start = function() { throw "not implemented"; } this.End = function() { throw "not implemented"; } this.GameReset = function() { throw "not implemented"; } }

Přizpůsobíme původní hru této fasádě:

// C++ class ArcadeGame : public IArcade { Player* p; Game* g; public: ArcadeGame(Player* p, Game* g) : p(p), g(g) { } void moveLeft() { p->moveLeft(); } void moveRight() { p->moveRight(); } void PlayerReset() { p->Reset(); } void Start() { g->Start(); } void End() { g->End(); } void GameReset() { g->Reset(); } }; // Javascript function ArcadeGame(player,game) { IArcade.call(this); this.moveLeft = function() { player.moveLeft(); } this.moveRight = function() { player.moveRight(); } this.PlayerReset = function() { player.Reset(); } this.Start = function() { game.Start(); } this.End = function() { game.End(); } this.GameReset = function() { game.Reset(); } }

Klientský kód (tam, kde se tyto třídy volají) upravíme na použití této fasády. Potom můžeme vytvořit jinou hru, aniž bychom museli upravovat klientský kód: prostě jen jinak imlementujeme fasádu, ne nutně pomocí stejných tříd. Například takto:

// C++ class ArcadeGame2 : public IArcade { Rival* p1, * p2, * p; Game* g; Engine* e; private: void switchPlayer() { p = p==p1 ? p2 : p1; } public: ArcadeGame(Rival* p1, Rival* p2, Game* g, Engine* e) : p1(p1), p2(p2), g(g), e(e) { player = p1; } void moveLeft() { switchPlayer(); p->moveLeft(); } void moveRight() { switchPlayer(); p->moveRight(); } void PlayerReset() { p->Reset(); } void Start() { g->Start(); } void End() { g->End(); } void GameReset() { e->Reset(); } }; // Javascript function ArcadeGame2(rival1,rival2,game,engine) { var p = rival1; function switchPlayer() { p = p==rival1 ? rival2 : rival1; } this.moveLeft() { switchPlayer(); p.moveLeft(); } this.moveRight() { switchPlayer(); p.moveRight(); } this.PlayerReset() { p.Reset(); } this.Start() { game.Start(); } this.End() { game.End(); } this.GameReset() { engine.Reset(); } }

Oba vzory z klientského kódu abstrahují detaily pomocí rozhraní. Zatímco Fasáda umožňuje vyřešit tentýž problém pomocí jiné struktury tříd, Most se používá k přepínání (nebo náhradě) jedné třídy druhou. Most plní funkci obsluhy (controlleru) v architektonickém vzoru MVC, zatímco Fasáda se používá jako nástroj návod na implementaci problému. Rozdíl je tedy ve způsobu použití: zavedením přepínače se z Fasády stává Most, odstraněním přepínače (tedy přímé volání rozhraní v klientském kódu) se z Mostu stává Fasáda.