Constructorul virtual (Factory Method)
Definitie
Acest sablon defineste o interfata pentru crearea unui obiect, dar lasa subclasele sa decida ce clasa trebuie instantiata. Practic, presupune ca o clasa paseaza subclaselor sale sarcina instantierii.
Context
Consideram un generator de aplicatii (framework for applications)
gen desktop. Asemenea aplicatii sunt de regula concepute sa lucreze cu
documente. Un generator de aplicatii desktop include abstractiuni care
sa asigure suportul pentru operatii ca: deschiderea, crearea sau salvarea
unui document. Abstractiunile de baza sunt clasele Application
si Document. Ambele sunt clase abstracte, iar clientii trebuie
sa creeze subclase din ele, pentru a-si defini propriile aplicatii. Pentru
a genera o aplicatie de desenare, de exemplu, trebuie sa se defineasca
subclasele DrawingApplication, respectiv DrawingDocument.
Clasa Application are ca sarcina gestionarea
documentelor, actionand asupra acestora la cerere (de exemplu cand utilizatorul
selecteaza o comanda New sau Open din meniu).
Deoarece subclasa lui Document care trebuie
instantiata este specifica aplicatiei, clasa Application
n-o cunoaste dinainte, deci nu stie CE sa intantieze. Application
stie doar CAND trebuie creat un nou document. Apare astfel o dilema
("dilema vampirului :)" ): generatorul de aplicatii trebuie sa instantieze
o anumita clasa, dar el nu cunoaste decat clase abstracte, care nu pot
fi instantiate.
Sablonul FactoryMethod
ofera o solutie acestei probleme. El propune incapsularea intr-un obiect
a cunostintelor referitoare la subclasa lui Document care
trebuie instantiata, si pasarea acestor cunostinte in afara generatorului
de aplicatii. In figura de mai jos este ilustrat acest principiu de lucru:
Document* CreateDocument( ) {
return new MyDocument; } |
Presupunand ca in clasa Application avem
un membru docs, care reprezinta o lista de documente gestionate
de aplicatie, atunci operatia NewDocument ar putea fi de
forma:
void NewDocument( ) {
Document *doc = CreateDocument( ); docs.Add(doc); doc -> Open( ); } |
Aceasta operatie va fi mostenita in clasa MyApplication si, deci, prin intermediul metodei CreateDocument va instantia de fapt obiecte de tip MyDocument. Vom numi operatia CreateDocument metoda de generare (factory method), deoarece ea este responsabila cu "manufacturarea" unui obiect. Prin intermediul acestei functii, redefinita in subclasele lui Application, practic putem sa modelam situatia in care clasa Application creaza obiecte fara sa stie de ce tip sunt ele.
Motivatii
Sablonul Factory Method se foloseste cand:
Solutie
Sablonul Factory Method
pune in legatura un obiect independent de aplicatie cu unul dependent de
aplicatie care va fi delegat sa creeze alte obiecte dependente de aplicatie.
Structura de obiecte propusa este cea din figura:
Consecinte
La implementarea sablonului FactoryMethod apar urmatoarele aspecte:
Clasa Creator poate fi o clasa abstracta, iar metoda de generare nu are implementare sau poate fi o clasa concreta, metoda de generare avand o implementare implicita.Metoda de generare poate fi proiectata astfel incat sa genereze mai multe tipuri de obiecte Produs, prevazandu-i un parametru care sa identifice tipul obiectului de creat, ca in secventa de mai jos:
- In primul caz, subclasele CreatorConcret sunt obligate sa-si defineasca propriile implementari ale metodei de generare si aceasta situatie apare de obicei acolo unde clasa Creator nu poate prevedea care vor fi clasele ProdusConcret ce se vor instantia.
- In al doilea caz clasele CreatorConcret vor utiliza metoda de generare mai mult in scopul asigurarii flexibilitatii. Proiectantii acestor subclase vor putea oricand modifica clasa obiectelor create implicit de Creator, redefinind metoda de generare.
class Creator {
public:
virtual Produs* Create(ProdusId);
//. . .
};
Produs* Creator::Create(ProdusId id) {
if (id == Id1) return new UnProdus;
if (id == Id2) return new AltProdus;
//s.a.m.d. pentru alte subclase ale clasei Produs
return 0; //daca id nu are nici una din valorile asteptate
}Redefinind metoda Create intr-o subclasa a lui Creator, putem extinde sau modifica lista obiectelor care vor fi create, introducand noi valori pentru parametrul de tip ProdusId.
O varianta mai flexibila decat parametrizarea metodei de generare este aceea de a folosi o variabila membru a clasei Creator pentru a memora informatii despre clasa obiectelor de creat. In felul acesta nici nu mai este nevoie sa derivam clasa creator.Daca limbajul de implementare este C++, metoda de generare este intotdeauna o functie virtuala, adesea chiar pur virtuala. Aici trebuie precizat ca trebuie avut grija ca in constructorul clasei Creator sa nu se apeleze metoda de generare, deoarece la momentul respectiv, obiectul de tip CreatorConcret inca nu este initializat, deci functia CreatorConcret::FactoryMethod inca nu este disponibila.
Tot aici se recomanda ca accesul la obiectele Produs sa se realizeze doar prin intermediul unor accesori care creaza obiectele respective la cerere, asa ca in secventa urmatoare:
class Creator {
public:
Creator ( ): _produs(0) { }
Produs* GetProdus( );
protected:
virtual Produs* Create( );
private:
Produs* _produs;
//. . .
};
Produs* Creator::GetProdus( ) {
if (_produs == 0) _produs = Create( );
return _produs;
}Aceasta tehnica se mai numeste initializare intarziata (lazy initialization).
Daca nu se doreste derivarea clasei Creator, se pot folosi in loc template-uri:
class Creator {
public:
virtual Produs* Create( ) = 0;
//. . .
};
template <class T> class StandardCreator: public Creator {
public:
virtual Produs* Create( ) { return new T; }
};
class UnProdus : public Produs {
//. . .
};
StandardCreator <UnProdus> unCreator;Se recomanda utilizarea consecventa a unor conventii legate de numele operatiilor cu rol de metode de generare, cum ar fi:
Produs* DoMakeProdus( );
Tema
Se cere sa se aplice sablonul FactoryMethod
la realizarea urmatorului ansamblu de clase: