/ janturon.cz / Od kodéra k analytikovi / Abstraktní továrna a Builder

Abstraktní továrna a Builder

Abstraktní továrna

S pojmem továrna jsou spjaty tři vzory: Tovární metoda je náhrada konstruktoru vytvářející objekt. Továrna je třída implementující rozhraní. Abstraktní továrna je třída tvořící Továrny.

Mějme třídu Ticket a její tři továrny:

struct Ticket { property<int> Price; virtual void Buy()=0; }; // Airplane Factory creating Ticket struct AirplaneTicket : public Ticket { AirplaneTicket() { Price = 120; } void Buy() { cout << "buying airplane ticket"; } }; // Train Factory creating Ticket struct TrainTicket : public Ticket { TrainTicket() { Price = 90; } void Buy() { cout << "buying train ticket"; } }; // Boat Factory creating Ticket struct BoatTicket : public Ticket { BoatTicket() { Price = 60; } void Buy() { cout << "buying boat ticket"; } };

Abstraktní továrna je pravidlo určující, která z továren se použije:

// Abstract Factory invoking Airplane/Train/Boat factory Ticket* TicketDialog() { cout << "Slow (s) / Medium (m) / Fast (f) ? "; char request; cin.get(request); switch(request) { case 'f': return new AirplaneTicket(); case 's': return new BoatTicket(); } return new TrainTicket(); }

V klientovi pak použijeme abstraktní metodu, která sama vybere vhodnou továrnu:

int main() { Ticket* ticket = TicketDialog(); ticket->Buy(); return 0; }

Jiný příklad

V jazycích C a C++ je díky preprocesoru možná i alternativa továrny, která nemusí být nutně objektová. Například pro pozastavení běhu programu je pod Windows používána funkce sleep knihovny <windows.h>, zatímco na Linuxu funkce usleep z knihovny <unistd.h>. Preprocesor vygeneruje tu správnou funkci v závislosti na OS, na kterém je program kompilován:

#ifdef _WIN32 #include <windows.h> inline void sleep(unsigned pMilliseconds) { ::Sleep(pMilliseconds); } #else #include <unistd.h> inline void sleep(unsigned pMilliseconds) { static const unsigned MilliToMicro = 1000; ::usleep(pmilliseconds * MilliToMicro); } #endif

Nyní se lze spolehnout na to, že následující kód poběží stejně pod oběma systémy:

int main() { cout << "Zzzz..."; sleep(1000); // továrna dosadí správnou funkci cout << "Wake up!; }

Pokud se má továrna rozhodnout o tvorbě objektu až za běhu (jako v předchozím příkladě), tento efektivnější způsob nelze použít.

Builder

Vzor Builder je podobný abstraktní továrně, ale příkazy k nastavení objektu jsou přesunuty do samostatné třídy (označované jako Director). Tento postup volíme, pokud obchodní logika aplikace velí spravovat objekty centrálně.

Tvořme objekty třídy Info:

struct Info { property Data; };

Ty nastaví třída Builder metodou Ask(). (Builder by mohl obsahovat ještě další metody pro úpravu Info.)

class Builder { protected: Info info; public: virtual void Ask()=0; Info getInfo() { return info; } };

V případě abstraktní továrny by Builder dědil z Info a jeho konstruktor (popřípadě tovární metoda) by vytvářela objekt z Info zděděný.

Zde provádění metod pro nastavení objektu (v tomto případě pouze Ask) přenecháme třídě Director:

struct Director { static void Construct(Builder* tool) { tool->Ask(); cout << "Tool was created." << endl; } };

Nyní můžeme napsat specializované Buildery, které nastavují objekty Info: řekněme, že jeden ho nastaví informací o věku, druhý o pohlaví:

class AgeRequest : public Builder { int age; string Answer() { if(age<0) return "unborn"; if(age<6) return "little"; if(age<20) return "young"; if(age<40) return "grown"; if(age<60) return "mature"; if(age<80) return "old"; if(age<120) return "aged"; return "undead"; } public: void Ask() { cout << "Age: "; cin.sync(); cin >> age; info.Data = Answer(); } }; class GenderRequest : public Builder { public: void Ask() { cout << "Are you male? (y/n): "; char response; cin.sync(); cin.get(response); bool male = response=='y' || response=='Y'; info.Data = male ? "man" : "woman"; } };

Celou konstrukci pak můžeme použít následovně:

int main() { // Builder k tvorbě objektů, zde typu Info Builder *Age = new AgeRequest, *Gender = new GenderRequest; // Director k sestavení objektů Info z metod Builderu Director::Construct(Age); Director::Construct(Gender); // získání objektů Info z Builderů Info AgeInfo = Age->getInfo(), GenderInfo = Gender->getInfo(); // výstup cout << "Hello, " << AgeInfo.Data << " " << GenderInfo.Data; }