Cam orice carte pe subiect vad ca incepe cu Fabrica Abstracta (Abstract Factory Design Pattern), asa ca momentan ma voi conforma.
Intentiile modelului Abstract Factory
1. a se pune la dispozitie o interfata pentru crearea familiilor de obiecte inrudite sau dependente, fara a specifica clase concrete2. o ierarhie care incapsuleaza mai multe posibile "platforme" si construirea unei suite de "produse"
3. operatorul new este considerat daunator
Asa spune la carte. Apoi explica mai departe ca o aplicatie care vrea sa fie portabila trebuie sa incapsuleze dependintele de platforma, gen sistem de operare, sistem de ferestre, baze de date etc. In cazul in care aceasta nevoie de dependinte nu a fost luata in calcul de la bun inceput, codul ajunge sa fie presarat cu diverse ramuri conditionale pentru include/exclude varii bucati de functionalitate, si asta nu-i bine ca duce la "spaghetti code".
Abstract Factory abstractizeaza crearea de familii de obiecte inrudite sau dependente ... vezi intentia 1. Obiectul fabrica are sarcina de a pune la dispozitie servicii de creare pentru toate obiectele din platforma. Clientii acestui serviciu nu creeaza obiecte de platforma direct, ci le cer de la fabrica.
Avantajul acestei abordari este ca tipurile concrete de obiecte apar intr-un singur loc in aplicatie - cand sunt instantiate. Pentru a inlocui o intreaga familie de produse este suficient sa se instantieze o fabrica abstracta diferita.
Citind un pic pe net vad ca este dat ca exemplu bun de fabrica abstracta clasa DbProviderFactory din .NET, pentru ca te lasa sa creezi obiecte inrudite (conexiuni, comenzi, ...) legate de o anumita implementare de provider de baza de date (Oracle, SQL Server, ...). Cuvantul magic ar fi doua: obiecte inrudite.
Asa ca hai sa cream o fabrica de vehicule pe doua roti, asta pentru ca tot imi place mie sa ma dau cu bicla si motorul. Va fi o clasa fabrica-abstracta, care va defini doua produse: bicicleta si motocicleta. Fiecare din aceste doua tipuri de produse va implementa o metoda Run, pe care o apelam ca sa mergem cu vehiculul respectiv.
Un eventual client ar avea urmatorii pasi de facut:
- se duce la fabrica
- alege tipul de vehicul, pe care-l cumpara
- circula cu vehiculul respectiv
In lumea asta din articol exista doua fabrici de vehicule: Honda si Rich Bike. Amandoua fac atat biciclete cat si motociclete.
using System; namespace AbstractFactory { class Program { static void Main(string[] args) { new Client(new RichBikeFactory(), "bike").DoStuff(); Console.ReadKey(); // asteapta o tasta de la utilizator } } // Vehicul, clasa abstracta, stie doar sa mearga abstract class AbstractTwoWheeler { public abstract void Run(); } // Clasa abstracta de fabrica abstract class AbstractTwoWheelerFactory { public abstract AbstractTwoWheeler CreateBike(); public abstract AbstractTwoWheeler CreateMotorcycle(); } // Fabrica Honda face biciclete si motociclete Honda class HondaFactory : AbstractTwoWheelerFactory { public override AbstractTwoWheeler CreateBike() { return new HondaBike(); } public override AbstractTwoWheeler CreateMotorcycle() { return new HondaMotorcycle(); } } // care merg class HondaBike : AbstractTwoWheeler { public override void Run() { Console.WriteLine("I run on foot power."); } } class HondaMotorcycle : AbstractTwoWheeler { public override void Run() { Console.WriteLine("Nice and smooth, just rike they taught me in Japan."); } } /* Fabrica Rich Bike face biciclete si motociclete Rich Bike, desigur. In fabrica se pot face si alte activitati, transparente clientului, gen sa dai un polish mic la produs inainte sa-l vinzi, ca sa iei ochii. */ class RichBikeFactory : AbstractTwoWheelerFactory { public override AbstractTwoWheeler CreateBike() { var rbb = new RichBikeBike(); rbb.PrepareForSale(); return rbb; } public override AbstractTwoWheeler CreateMotorcycle() { return new RichBikeMotorcycle(); } } class RichBikeBike : AbstractTwoWheeler { public override void Run() { Console.WriteLine("Domnu domnu mergeti pa langa ea sa nu se strica."); } public void PrepareForSale() { Console.WriteLine("Hai baieti dati-i un polish sa arate bine! A, si lipiti-i pipa cu superglu, ca aproape cade!"); } } class RichBikeMotorcycle : AbstractTwoWheeler { public override void Run() { Console.WriteLine("Pam-pam-pam: azi merg frumos. Pana se strica ceva."); } } /* Clientul stie doar ca exista aceste fabrici, care fac bicle si motoare. Se asteapta sa primeasca un vehicul care merge. Punct. */ class Client { AbstractTwoWheeler _twoWheeler; public Client(AbstractTwoWheelerFactory i_factory, string i_kind) { this._twoWheeler = i_kind == "bike" ? i_factory.CreateBike() : i_factory.CreateMotorcycle(); } public void DoStuff() { _twoWheeler.Run(); } } }
Dupa cate vedem, clientului i se spune la ce fabrica sa mearga, dar isi alege el tipul de vehicul. Nu are nevoie sa stie ca, de exemplu, o bicicleta Rich Bike are pipa lipita cu clei.
Din punct de vedere al programarii ce imi da design pattern-ul asta? pai, daca sunt client al fabricii imi e destul de usor sa zic: da-mi o fabrica din aia si imi scot eu din ea toate obiectele de care am nevoie. Da-mi radacina de arbore si apoi imi scot eu o padure de acolo. Zi-mi cu cine vorbesc de la firma X si ii conving eu sa ne cumpere produsul.
Lucrul asta ajuta atunci cand vrei sa decuplezi implementarile acelor servicii de codul care le foloseste.
Pot fi folosite clase abstracte, sau interfete. Clasele abstracte din exemplul de mai sus sunt oarecum inutile, ca si clase, daca foloseam interfete era mai bine, intrucat in clasele respective nu am pus membri si nici implementare de functionalitate. Daca, sa zicem, as vrea sa tin minte cate vehicule a produs fiecare fabrica probabil ca ar avea sens sa las clasa abstracta si sa pun in ea un camp numeric, care sa fie incrementat la crearea fiecarui vehicul. Un alt avantaj la folosirea interfetelor in cazul de fata ar fi ca ma lasa sa consum acea clasa unica pe care o pot mosteni in C# pe alta clasa, care chiar aduce functionalitate noua.
OK, acum stim cam ce-i cu fabrica abstracta: ne ajuta sa selectam intr-un singur punct una din mai multe multimi de clase cu functionalitate similara. Exista si alte modele creationale, dintre care vreau sa mai discut in acest articol despre urmatoarele: metoda fabrica (Factory Method) si constructor (Builder).
Intentiile modelului Factory Method
La carte zice asa:
- defineste o interfata pentru crearea unui obiect, dar lasa subclasele sa decida care clasa sa fie instantiata
- defineste un constructor "virtual"
- operatorul new este considerat daunator
Hai sa zicem asa: modelul Factory Method merge folosit atunci cand vrei sa obtii un obiect al carui tip si caracteristici sa depinda de niste parametri pe care-i specifici tu si/sau de starea interna a fabricii pe care o folosesti.
Un exemplu bun e mai sus: constructorul clasei Client primeste doi parametri: o fabrica si un string. In functie de string-ul respectiv constructorul va instantia in obiectul respectiv un obiect de un tip sau altul: daca i-am transmis "bike" ca parametru, va crea un obiect de tip bicicleta, altfel va crea un obiect de tip motocicleta.
Pentru a fi mai evidenta folosirea modelului Metoda Fabrica putem rescrie clasa Client astfel:
using System; namespace AbstractFactory { class Client { AbstractTwoWheeler _twoWheeler; public Client(AbstractTwoWheelerFactory i_factory, string i_kind) { _twoWheeler = GetMeMyWheels(i_factory, i_kind); } private void GetMeMyWheels(AbstractTwoWheelerFactory i_factory, string i_kind){ return i_kind == "bike" ? i_factory.CreateBike() : i_factory.CreateMotorcycle(); } public void DoStuff() { _twoWheeler.Run(); } } }
Aici metoda GetMeMyWheels primeste acel parametru pe baza caruia poate sa discrimineze tipul de obiect dorit si instantiaza un obiect nou pe care apoi il returneaza la apelant.
Dar o metoda fabrica nu trebuie neaparat sa instantieze obiecte noi, poate sa returneze obiecte existente. Ideea de baza aici ar fi urmatoarea: clientul doreste un obiect. Poate sa dea niste parametri, poate sa se bazeze pe starea fabricii, poate sa nu dea nici o alta informatie suplimentara, asta e chestiune de implementare. Ceea ce este esential este ca acel client nu doreste sa instantieze el un obiect, ci il cere de la o fabrica de obiecte.
Fabricile Abstracte (Abstract Factory) sunt deseori implementate folosind Metode Fabrica (Factory Method), dar nu neaparat. Diferenta intre Fabrica Abstracta si Metoda Fabrica vine din necesitatea unei colectii de clase in cazul Fabricii Abstracte. Cand e nevoie de un singur obiect, merge o Metoda Fabrica. De multe ori se incepe de la ceva mai simplu, implementat cu o Metoda Fabrica, si se poate ajunge la modele creationale mai complexe, gen Fabrica Abstracta, Constructor (Builder) sau Prototip (Prototype).
Intentiile Modelului Builder
Cartea zice:
- separa constructia unui obiect complex de reprezentarea sa, astfel incat acelasi proces de constructie sa poata crea reprezentari diferite
- analizeaza o reprezentare complexa, creeaza una din mai multe tinte
Pot sa spun ca iar e un pic prea abstract pentru mine, asa ca citim mai jos pe pagina respectiva. Si gasim ca este vorba despre separarea algoritmului de construire a respectivului obiect complex de algoritmul care coordoneaza construirea lui.
Avem urmatoarele entitati implicate: clientul cere directorului sa construiasca un obiect folosind un constructor. Constructorul este o interfata sau o clasa abstracta, implementata de alte clase - constructori concreti - care sunt folosite propriu-zis la crearea obiectului.
Clientul specifica directorului pe care anume constructor concret sa-l foloseasca. Clientul se asteapta sa primeasca un singur obiect, la fel ca in cazul unei metode fabrica, si nu un grup de obiecte, ca in cazul fabricii abstracte. Dupa ce a plasat cererea, clientul asteapta rezultatul. Probabil ca la sfarsit clientul va dori sa foloseasca obiectul care i se returneaza, ca altfel nu ar avea rost sa-l ceara.
Directorul stie care sunt pasii necesari pentru a construi un obiect de tipul cerut. Lui trebuie doar sa-i zici ce constructor concret sa foloseasca, si apoi el stie sa dea comenzile respective. La sfarsit va returna obiectul dorit de client.
Obiectul este rezultatul procesului de constructie realizat de constructorul concret.
In exemplele din carte constructorul este ceva generic, abstract, implementat de clase concrete. Nu sunt convins ca asta e singura modalitate in care se poate folosi modelul acesta.
Conditie importanta pare sa fie ca obiectul sa fie complex, si construibil in mai multi pasi. Reformuland - merge folosit modelul asta atunci cand obiectul pe care il vrei construit este complex. Si presupun ca daca exista un singur tip de obiecte pe care le poti construi nu ai nevoie de mai multi constructori concreti, adica ai undeva pur si simplu o secventa de pasi pe care-i urmezi, deci are sens sa-l folosesti atunci cand ai mai multe tipuri de obiecte inrudite.
Putem continua exemplul de mai sus cu vehiculele pe 2 roti, similar cu exemplul de pe dofactory.com. O sa facem fabricile sa fie constructori concreti.
Avem urmatoarele entitati implicate: clientul cere directorului sa construiasca un obiect folosind un constructor. Constructorul este o interfata sau o clasa abstracta, implementata de alte clase - constructori concreti - care sunt folosite propriu-zis la crearea obiectului.
Clientul specifica directorului pe care anume constructor concret sa-l foloseasca. Clientul se asteapta sa primeasca un singur obiect, la fel ca in cazul unei metode fabrica, si nu un grup de obiecte, ca in cazul fabricii abstracte. Dupa ce a plasat cererea, clientul asteapta rezultatul. Probabil ca la sfarsit clientul va dori sa foloseasca obiectul care i se returneaza, ca altfel nu ar avea rost sa-l ceara.
Directorul stie care sunt pasii necesari pentru a construi un obiect de tipul cerut. Lui trebuie doar sa-i zici ce constructor concret sa foloseasca, si apoi el stie sa dea comenzile respective. La sfarsit va returna obiectul dorit de client.
Obiectul este rezultatul procesului de constructie realizat de constructorul concret.
In exemplele din carte constructorul este ceva generic, abstract, implementat de clase concrete. Nu sunt convins ca asta e singura modalitate in care se poate folosi modelul acesta.
Conditie importanta pare sa fie ca obiectul sa fie complex, si construibil in mai multi pasi. Reformuland - merge folosit modelul asta atunci cand obiectul pe care il vrei construit este complex. Si presupun ca daca exista un singur tip de obiecte pe care le poti construi nu ai nevoie de mai multi constructori concreti, adica ai undeva pur si simplu o secventa de pasi pe care-i urmezi, deci are sens sa-l folosesti atunci cand ai mai multe tipuri de obiecte inrudite.
Putem continua exemplul de mai sus cu vehiculele pe 2 roti, similar cu exemplul de pe dofactory.com. O sa facem fabricile sa fie constructori concreti.
using System; using System.Collections.Generic; namespace Builder { class Program { static void Main(string[] args) { Director director = new Director(); AbstractTwoWheelerFactory factory1 = new BicycleFactory(); director.BuildVehicle(factory1); TwoWheeler wheeler1 = factory1.TwoWheeler; wheeler1.Describe(); AbstractTwoWheelerFactory factory2 = new MotorcycleFactory(); director.BuildVehicle(factory2); TwoWheeler wheeler2 = factory2.TwoWheeler; wheeler2.Describe(); Console.ReadKey(); } } class TwoWheeler { public TwoWheeler(string i_name) { _name = i_name; } private Dictionary<string, string> _parts = new Dictionary<string, string>(); public string this[string i_key] { get { return _parts[i_key]; } set { _parts[i_key] = value; } } private string _name; public string Name { get { return _name; } set { _name = value; } } public void Describe() { Console.WriteLine("{0} are urmatoarele componente: ", _name); foreach (var pair in _parts) { Console.WriteLine("{0}: {1}", pair.Key, pair.Value); } Console.WriteLine(); } } class Director { public void BuildVehicle(AbstractTwoWheelerFactory i_factory) { i_factory.BuildFrame(); i_factory.BuildEngine(); i_factory.BuildHorn(); i_factory.BuildWheels(); } } // Clasa abstracta de fabrica abstract class AbstractTwoWheelerFactory { protected TwoWheeler _twoWheeler; public TwoWheeler TwoWheeler { get { return _twoWheeler; } } // Metodele de constructie public abstract void BuildFrame(); public abstract void BuildEngine(); public abstract void BuildHorn(); public abstract void BuildWheels(); } class BicycleFactory : AbstractTwoWheelerFactory { public BicycleFactory () { _twoWheeler = new TwoWheeler("Bicla"); } public override void BuildFrame() { _twoWheeler["frame"] = "cadru de aluminiu"; } public override void BuildEngine() { _twoWheeler["engine"] = "picioare la purtator"; } public override void BuildHorn() { _twoWheeler["horn"] = "trompetica sau strigat din gura"; } public override void BuildWheels() { _twoWheeler["wheels"] = "26,26"; } } class MotorcycleFactory : AbstractTwoWheelerFactory { public MotorcycleFactory() { _twoWheeler = new TwoWheeler("Motor"); } public override void BuildFrame() { _twoWheeler["frame"] = "cadru de otel"; } public override void BuildEngine() { _twoWheeler["engine"] = "500cmc, 2 cilindri in paralel"; } public override void BuildHorn() { _twoWheeler["horn"] = "claxon"; } public override void BuildWheels() { _twoWheeler["wheels"] = "100/90-16,120/90-16"; } } }
Macar n-am copiat chiar tot, am incercat sa inovez un pic. Avem componentele:
- vehiculul
- directorul, care stie sa coordoneze constructia vehiculului
- modelul abstract de fabrica - diferenta fata de Abstract Factory este ca aici nu se pune problema sa las un client sa instantieze un numar de clase inrudite, ci pur si simplu expun niste metode, pe care ma astept sa le apeleze cineva care stie ce face, cum ar fi un director
- implementarile concrete de fabrici, care creeaza efectiv vehiculul, si care-s coordonate de director
- clientul cere si primeste vehiculul
Cam asta ar fi, cred ca macar mie imi sunt mai clare notiunile astea de-acum.
Spor!
Niciun comentariu:
Trimiteți un comentariu
S-ar putea sa nu vedeti comentariul aparand imediat, asta inseamna ca el asteapta aprobarea mea. Aceasta e o masura anti-SPAM.