Różnice między wybraną wersją a wersją aktualną.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
fabryka_obiektow [2008/04/16 01:28] sdybiec usunięto |
fabryka_obiektow [2008/04/16 21:42] (aktualna) kzbikows |
||
|---|---|---|---|
| Linia 1: | Linia 1: | ||
| <code cpp> | <code cpp> | ||
| - | /*********************************************************************************** | + | /*Kamil Żbikowski - gr H5ISI - PRACA DOMOWA Z PRZEDMIOTU ZPR | |
| - | * Autor: Sławomir Dybiec | + | |_______________________________________________________________________________| |
| - | * Wzorzec: fabryka prototypów (rejestrowanie obiektów) | + | | Fabryka Obiektów - rejestracja metod fabrycznych | |
| - | * | + | | | |
| - | * Zastosowanie: Wzorca tego używamy, kiedy mamy informacje o typie obiektu który | + | |Problem: Tworzenie obiektu jest w jezyku c++ statytyczne. Nie zawiera on | |
| - | * chcemy utworzyć. Programista w momencie pisania kodu nie może przewidzieć, jakiej | + | | mechanizmu tworzenia obiektów w sposób dynamiczny. Pojęcia klasy| |
| - | * klasy obiekt należy utworzyć. Informacja ta znana będzie dopiero podczas wykonywania | + | | i obiektu są różne. Klasa jest tworzona przez programistę - jest| |
| - | * programu(np. na podstawie informacji zawartych w pliku). | + | | definicją obiektu, który to z kolei jest tworzony w trakcie | |
| - | * | + | | działania programu. Często jednak pojawia się potrzeba aby | |
| - | * Opis: Wzorzec ten jest właściwie połączeniem wzorca fabryki obiektów oraz wzorca | + | | w sposób dynamiczny generować obiekty (np. odczyt/zapis obiektów| |
| - | * prototypu. Uwaga fabryka prototypów często jest również singletonem. Zachęcam do | + | | na dysk) albo np. pozostawić klientom możliwość modyfikacji | |
| - | * zapoznania się z wymienionymi przeze mnie wzorcami. | + | | zachowania obiektów za pomocą polimorfizmu poprzez wywołanie | |
| - | * | + | | funkcji wirtualnej Create zdefiniowanej przez klasę wysokiego | |
| - | * Przykład: Załóżmy, że posiadamy klasy służące do przetwarzania plików graficznych, | + | | poziomu. Rozwiązanie "siłowe" poprzez zastosowanie instrukcji | |
| - | * jednak inna klasa odpowiada za obsługę obrazów zapisanych w formacie GIF, a innej | + | | switch nie jest najlepszym rozwiązaniem: | |
| - | * klasy używamy do obrazów zapisanych jako JPG. W momencie tworzenia kodu nie wiemy, | + | | - mija sie z ideą obiektowości | |
| - | * jakiego typu będzie plik który zechce przetworzyć użytkownik naszej biblioteki. | + | | - w jednym pliku źródłowym jest zawarta informacja | |
| - | * W tym momencie z pomocą przychodzi nam wzorzec fabryki prototypów. | + | | o wszystkich podtypach co czyni go wąskim gardłem, | |
| - | */ | + | | jeśli chodzi o zależności kompilacji i utrzmanie kodu.| |
| + | | - modyfiukując kod musimy go zmieniać w wielu miejscach | | ||
| + | |Rozwiąznie: Naprzeciw powyższemu problemowi wychodzi wzorzec fabryki | | ||
| + | | obiektów z rejestracją metod fabrycznych, który w przybliżeniu | | ||
| + | | wygląda obrazuje poniższy schemat: | | ||
| + | | _______ _______________________ | | ||
| + | | |Produkt|<--------------| FabrykaObiektow | | | ||
| + | | |_______| |-----------------------| | | ||
| + | | ^ |+ StworzProdukt() | | | ||
| + | | | |+ RejestrujProduct() | | | ||
| + | | | |_______________________| | | ||
| + | | ___|_____ | | ||
| + | | |Produkt | | | ||
| + | | |Konkretny| | | ||
| + | | |_________| | | ||
| + | | | | ||
| + | | Produkt konkretny - fabryka dostarcza produkt w postaci obiektu | | ||
| + | | Produkt - abstrakcyjny typ podstawowy. Fabryka zwraca wskaźnik | | ||
| + | | do tego obiektu nie przekazując informacji o produkcie| | ||
| + | | konkretnym. | | ||
| + | | Informacje o konkretnym produkcie przechowywane są przez fabrykę| | ||
| + | | w mapie (np. std::map). Każdy konkretny produkt ma przydzielony | | ||
| + | | unikalny identyfiaktor na podstawie, którego jest identyfikowany| | ||
| + | | Własciwie jest przechowywany wskaźnik do funkcji zwracającej | | ||
| + | | wskaźnik do Produkt. | | ||
| + | | Rejestracja kolejnego produktu konkretnego odbywa się poprzez | | ||
| + | | podanie unikalnego identyfikatora danego Produktu Konkretnego | | ||
| + | | oraz wskaźnika na funkcję zwracającą wskaźnik na Produkt. | | ||
| + | | Dzięki zastosowaniu polimorfizmu wyeliminowaliśmy wady | | ||
| + | | rozwiązania siłowego. Teraz dodanie kolejnego Produktu | | ||
| + | | Konkretnego sprowadza się do stworzenia nowej klasy definiującej| | ||
| + | | go, zawierającej metodę postaci: | | ||
| + | | Produkt* StworzKonkretnyProdukt(){ | | ||
| + | | return new ProduktKonkretny; | | ||
| + | | } | | ||
| + | | Rejestracja natomiast polega na wywolaniu RegisterProduct na | | ||
| + | | rzecz wyżej wymienionej metody. Od tej chwili stworzenie | | ||
| + | | konkretnego produktu będzie polegało na wywołaniu metody | | ||
| + | | StworzProdukt z okrślonym id podanym w porcesie rejstracji | | ||
| + | | Czesto fabryka obiektów powinna być singletonem, ale to juz jest| | ||
| + | | temat na oddzielną pracę. | ||
| + | |______________________________________________________________________________*/ | ||
| #include <iostream> | #include <iostream> | ||
| - | #include <vector> | + | #include <map> |
| - | #include <assert.h> | + | |
| - | using namespace std;// dla wygody, żeby nie pisać wszędzie std | + | /** |
| - | + | * Szablon fabryki. Product - klasa abstrakcyjan, Id - identyfiaktor klasy, ProductCreator - | |
| - | //Typ pliku będzie reprezentowany poprzez obiekt string zawierający jego rozszerzenie | + | * wskaźnik na funkcję zwracającą wskaźnik na obiekt konkretny dziedziczący po Product |
| - | typedef std::string IMG_TYPE; | + | */ |
| - | + | template <class Product, class Id, class ProductCreator = Product* (*)()> | |
| - | //Funkcja pomocnicza zwracająca nam rozszerzenie pliku. | + | class Factory{ |
| - | string getFileExtension(const string & filename) | + | public: |
| - | { | + | //Metoda produkująca konkretne obiekty na podstawie ich identyfiaktorów. |
| - | string w; | + | Product* CreateProduct(const Id& id){ |
| - | int a; | + | typename Map::const_iterator i = associations.find(id); |
| - | a=filename.find_last_of("."); | + | if(i != associations.end()){ |
| - | if (a==-1 || a==static_cast<int>(filename.size())-1) | + | return (i->second()); //jeśli w mapie jest podany w parametrze klucz |
| - | { | + | //wywołaj powiązaną z nim funkcję |
| - | //Błąd plik nie posiada rozszerzenia | + | } |
| - | assert(0); | + | else |
| - | } | + | return NULL; //obsługa błędów - przykładowe rozwiązanie |
| - | w.assign(filename, a+1, filename.size()- a-1); | + | //można zaimplementować rozwiązanie z użyciem wyjątków |
| - | //Zwracamy rozszerzenie zapisane tylko dużymi literami | + | } |
| - | std::transform(w.begin(), w.end(), w.begin(), ::toupper); | + | //Metoda rejestrująca obiekty w fabryce. |
| - | return w; | + | bool Register(const Id& id, ProductCreator creator){ |
| - | } | + | return associations.insert( |
| - | + | typename Map::value_type(id, creator)).second; | |
| - | //Klasa reprezentująca dowolny obrazek. | + | } |
| - | class Image | + | //Metoda wyrejestrowująca obiekty. |
| - | { | + | bool UnRegister(const Id& id){ |
| - | public: | + | return associations.erase(id) == 1; |
| - | //Metoda zwracająca nam wskaźnik na konkretną instancje obiektu Image. | + | } |
| - | virtual Image* Clone()=0; | + | private: |
| - | virtual string getName()=0; | + | typedef std::map<Id, ProductCreator> Map; |
| - | /* | + | Map associations; //mapa służy do połączenia identyfiatora z danym wskźnikiem na funkcję |
| - | * W tym miejscu definiujemy metody obsługujące obrazek. Oczywiście są to także | + | //wymagane jest aby Id miało zaimplementowane operatory '==', '<' |
| - | * metody wirtualne. | + | // |
| - | */ | + | |
| }; | }; | ||
| - | /* | + | //Klasa abstrakcyjna Ford. Diedziczą po niej konkretne modele samochodów, które rejestrują się w fabryce |
| - | * Klasa reprezentująca obrazek zapisany w formacie JPG. Jest to jednocześnie | + | //a następnie są przez nią produkowane. |
| - | * implementacja wzorca prototyp. | + | class Ford{ |
| - | */ | + | public: |
| - | class ImageJPG : public Image { | + | //funkcja, która w każdej z klas dziedziczących po Ford |
| - | //Funkcja zwracająca nam wskaźnik na nowy obiekt klasy ImageJPG. | + | //wyświetla rodzaj danego obiektu |
| - | Image* Clone() | + | virtual void Info() = 0; |
| - | { | + | |
| - | return new ImageJPG(*this); | + | |
| - | } | + | |
| - | //Funkcja zwracająca srting z opisem klasy | + | |
| - | string getName() | + | |
| - | { | + | |
| - | return string("Klasa przetwarzająca pliki z rozszerzeniem JPG"); | + | |
| - | } | + | |
| }; | }; | ||
| - | + | Factory<Ford, int> FordFactory; //obiekt relizujący fabrykę samochodów marki Ford | |
| - | //Podobnie jak klasa ImageJPG. | + | //Każda z poniższych przestrzeni nazw zawierająca konkretne implementacje |
| - | class ImageGIF : public Image { | + | //klasy Ford może znajdować się w osobnym pliku. Chcąc dodać kolejną markę samochodu |
| - | Image* Clone() | + | //dodajemy kolejny plik z zawartością podobną jak każdy z namespace'ow. |
| - | { | + | namespace Focus{ |
| - | return new ImageGIF(*this); | + | static const int ID = 1; |
| - | } | + | class FocusFord : public Ford{ |
| - | string getName() | + | public: |
| - | { | + | virtual void Info(){ |
| - | return string("Klasa przetwarzająca pliki z rozszerzeniem GIF"); | + | std::cout << "Jestem Fordem Focus"<<std::endl; |
| - | } | + | } |
| - | }; | + | private: |
| - | /* | + | //konkretne właściwości danego modelu |
| - | * Klasa pomocnicza wiążąca prototyp klasy przetwarzającej obraz z odpowiednią wartością | + | }; |
| - | * pola IMG_TYPE. | + | Ford* CreateFocusFord(){ |
| - | */ | + | return new FocusFord; |
| - | class ImagePrototype | + | } |
| - | { | + | void StartProduce(){ |
| - | public: | + | FordFactory.Register(Focus::ID, &Focus::CreateFocusFord); |
| - | ImagePrototype(IMG_TYPE t, Image *p):type(t),prototype(p){} | + | } |
| - | IMG_TYPE type; | + | void StopProduce(){ |
| - | Image *prototype; | + | FordFactory.UnRegister(Focus::ID); |
| - | }; | + | } |
| - | //Klasa, której używamy do tworzenia obiektów. | + | |
| - | class ImagePrototypeFactory | + | |
| - | { | + | |
| - | std::vector<ImagePrototype> prototypes; | + | |
| - | public: | + | |
| - | /* | + | |
| - | * Funkcja służąca do dodawania nowych prototypów klas do przetwarzania obrazów. | + | |
| - | * Prototypy są powiązane z odpowiednią wartością pola IMG_TYPE. | + | |
| - | */ | + | |
| - | void addPrototype( IMG_TYPE img_type, Image * prototype ) | + | |
| - | { | + | |
| - | ImagePrototype ip = ImagePrototype(img_type, prototype); | + | |
| - | prototypes.push_back( ip ); | + | |
| - | } | + | |
| - | //Funkcja zwracająca wskaźnik na obiekt, który potrafi obsługiwać zadany typ obrazka. | + | |
| - | Image *getImage( IMG_TYPE img_type ) | + | |
| - | { | + | |
| - | int a = static_cast<int>(prototypes.size()); | + | |
| - | for(int i=0; i < a; ++i) | + | |
| - | if(prototypes[i].type == img_type) | + | |
| - | return prototypes[i].prototype->Clone(); | + | |
| - | + | ||
| - | //Błąd nieznany typ obrazka | + | |
| - | return NULL; | + | |
| - | } | + | |
| - | }; | + | |
| - | + | ||
| - | int main () | + | |
| - | { | + | |
| - | //Tworzymy fabrykę, która będzie konstruować odpowiedni obiekt w zależności od typu pliku. | + | |
| - | ImagePrototypeFactory ipf; | + | |
| - | Image * a; | + | |
| - | //Dodajemy prototypy klas, potrafiących przetwarzać odpowiednie pliki. | + | |
| - | ipf.addPrototype("GIF", new ImageGIF()); | + | |
| - | ipf.addPrototype("JPG", new ImageJPG()); | + | |
| - | + | ||
| - | //Prosimy Fabrykę prototypów o wskaźnik na obiekt klasy potrafiący przetwarzać plik xxx.gif | + | |
| - | a = ipf.getImage(getFileExtension("xxx.gif")); | + | |
| - | //Prosimy klasę o przedstawienie się :) | + | |
| - | cout << a->getName() << endl; | + | |
| - | + | ||
| - | return 0; | + | |
| - | + | ||
| - | /* | + | |
| - | * Wyjście: | + | |
| - | * Klasa przetwarzająca pliki z rozszerzeniem GIF | + | |
| - | */ | + | |
| } | } | ||
| - | + | namespace Galaxy{ | |
| - | /* | + | static const int ID = 2; |
| - | * Przy własnej implementacji tego wzorca, należy pamiętać o mechanizmie obsługi wyjątków, | + | class GalaxyFord : public Ford{ |
| - | * który tutaj został pominięty. | + | public: |
| - | * Innym częstym przykładem użycia tego wzorca jest zapis stanu naszego programu do pliku, | + | virtual void Info(){ |
| - | * wtedy podobnie jak w wyżej wymienionym przykładzie dla każdej klasy definiujemy | + | std::cout << "Jestem Fordem Galaxy"<<std::endl; |
| - | * odpowiedni identyfikator. Zapis każdego obiektu do pliku poprzedzony jest zapisaniem | + | } |
| - | * identyfikatora klasy do której należy obiekt. Następnie przy odczycie przekazujemy ten | + | private: |
| - | * identyfikator do odpowiedniej funkcji fabryki prototypów(w pokazanym przeze mnie | + | //konkretne właściwości danego modelu |
| - | * przykładzie była to funkcja getImage()), która to funkcja zwraca nam wskazanie na nowo | + | }; |
| - | * utworzony obiekt klasy powiązanej z tym identyfikatorem. | + | Ford* CreateGalaxyFord(){ |
| - | * Ważną cechą zaprezentowanego przeze mnie wzorca jest łatwość rozbudowy programu o dalsze klasy. | + | return new GalaxyFord; |
| - | * Nowo napisaną klasę(musi ona implementować wzorzec prototypu) rejestrujemy w naszej fabryce poprzez | + | } |
| - | * wywołanie funkcji addPrototype. | + | void StartProduce(){ |
| - | */ | + | FordFactory.Register(Galaxy::ID, &Galaxy::CreateGalaxyFord); |
| - | </code> | + | } |
| + | void StopProduce(){ | ||
| + | FordFactory.UnRegister(Galaxy::ID); | ||
| + | } | ||
| + | } | ||
| + | namespace Mondeo{ | ||
| + | static const int ID = 3; | ||
| + | class MondeoFord : public Ford{ | ||
| + | public: | ||
| + | virtual void Info(){ | ||
| + | std::cout << "Jestem Fordem Mondeo"<<std::endl; | ||
| + | } | ||
| + | private: | ||
| + | //konkretne właściwości danego modelu | ||
| + | }; | ||
| + | Ford* CreateMondeoFord(){ | ||
| + | return new MondeoFord; | ||
| + | } | ||
| + | void StartProduce(){ | ||
| + | FordFactory.Register(Mondeo::ID, &Mondeo::CreateMondeoFord); | ||
| + | } | ||
| + | void StopProduce(){ | ||
| + | FordFactory.UnRegister(Mondeo::ID); | ||
| + | } | ||
| + | } | ||
| + | //Przyklady użycia fabryki | ||
| + | int main(){ | ||
| + | //Trzy wskaźniki na obiekty typu Ford. Bedą się w nich znajdowały wskazania na konkretne klasy pochodne klasy Ford. | ||
| + | Ford* any1; | ||
| + | Ford* any2; | ||
| + | any1 = FordFactory.CreateProduct(Focus::ID); //Próbujemy wyprodukować Forda Focus'a | ||
| + | if(any1) | ||
| + | any1->Info(); //jeśli się udało to wyświetl informacje o samochodzie | ||
| + | //oczywiście w tym wypadku operacja się nie uda, ponieważ nie | ||
| + | //zarejestrowaliśmy tego podtypu w fabryce | ||
| + | else | ||
| + | //jeśli nie to wypisz stosowny komunikat | ||
| + | std::cout<<"Fabryka nie produkuje tego typu samochodu"<<std::endl; | ||
| + | //rejestrujemy podtyp w fabryce | ||
| + | Focus::StartProduce(); | ||
| + | any1 = FordFactory.CreateProduct(Focus::ID); | ||
| + | if(any1) | ||
| + | any1->Info(); //jeśli się udało to wyświetl informacje o samochodzie, teraz już dokonaliśmy rejestracji | ||
| + | else | ||
| + | //jeśli nie to wypisz stosowny komunikat | ||
| + | std::cout<<"Fabryka nie produkuje tego typu samochodu"<<std::endl; | ||
| + | //Zaczynamy produkować modele Galaxy | ||
| + | Galaxy::StartProduce(); | ||
| + | any2 = FordFactory.CreateProduct(Galaxy::ID); //produkujemy 1 egzemplarz Galaxy | ||
| + | if(any2) | ||
| + | any2->Info(); //jeśli się udało to wyświetl informacje o samochodzie, teraz już dokonaliśmy rejestracji | ||
| + | else | ||
| + | //jeśli nie to wypisz stosowny komunikat | ||
| + | std::cout<<"Fabryka nie produkuje tego typu samochodu"<<std::endl; | ||
| + | Galaxy::StopProduce(); | ||
| + | Focus::StopProduce(); | ||
| + | //staramy sie wyprodukować Galaxy i Focus po wyrejestrowaniu ich z fabryki | ||
| + | any1 = FordFactory.CreateProduct(Focus::ID); | ||
| + | any2 = FordFactory.CreateProduct(Galaxy::ID); | ||
| + | if(any2) | ||
| + | any2->Info(); //jeśli się udało to wyświetl informacje o samochodzie, teraz już dokonaliśmy rejestracji | ||
| + | else | ||
| + | //jeśli nie to wypisz stosowny komunikat | ||
| + | std::cout<<"Fabryka nie produkuje tego typu samochodu"<<std::endl; | ||
| + | if(any1) | ||
| + | any2->Info(); //jeśli się udało to wyświetl informacje o samochodzie, teraz już dokonaliśmy rejestracji | ||
| + | else | ||
| + | //jeśli nie to wypisz stosowny komunikat | ||
| + | std::cout<<"Fabryka nie produkuje tego typu samochodu"<<std::endl; | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||