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