Immutable Strategy

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:

In clasa Application operatia CreateDocument fie are prevazuta o implementare implicita, fie este fara implementare (in C++, de exemplu, ea va fi o functie pur virtuala). Aceasta operatie va fi redefinita in subclasa MyApplication astfel incat sa creeze un obiect de tip MyDocument si sa returneze o referinta la el:
 
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:

Operatia FactoryMethod din CreatorConcret va contine o instructiune de forma:
return new ProdusConcret;
iar in clasa Creator, celelalte functii, de exemplu AltaOperatie, vor putea apela FactoryMethod sub forma:
Produs *p = FactoryMethod;
Rolurile claselor componente din structura de mai sus sunt:


Consecinte

Implementare

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:
 
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:

Immutable Strategy