/*********************************************************************************** * Autor: Sławomir Dybiec * Wzorzec: fabryka prototypów (rejestrowanie obiektów) * * Zastosowanie: Wzorca tego używamy, kiedy mamy informacje o typie obiektu który * chcemy utworzyć. Programista w momencie pisania kodu nie może przewidzieć, jakiej * klasy obiekt należy utworzyć. Informacja ta znana będzie dopiero podczas wykonywania * programu(np. na podstawie informacji zawartych w pliku). * * Opis: Wzorzec ten jest właściwie połączeniem wzorca fabryki obiektów oraz wzorca * prototypu. Uwaga fabryka prototypów często jest również singletonem. Zachęcam do * zapoznania się z wymienionymi przeze mnie wzorcami. * * Przykład: Załóżmy, że posiadamy klasy służące do przetwarzania plików graficznych, * jednak inna klasa odpowiada za obsługę obrazów zapisanych w formacie GIF, a innej * klasy używamy do obrazów zapisanych jako JPG. W momencie tworzenia kodu nie wiemy, * jakiego typu będzie plik który zechce przetworzyć użytkownik naszej biblioteki. * W tym momencie z pomocą przychodzi nam wzorzec fabryki prototypów. */ #include #include #include using namespace std;// dla wygody, żeby nie pisać wszędzie std //Typ pliku będzie reprezentowany poprzez obiekt string zawierający jego rozszerzenie typedef std::string IMG_TYPE; //Funkcja pomocnicza zwracająca nam rozszerzenie pliku. string getFileExtension(const string & filename) { string w; int a; a=filename.find_last_of("."); if (a==-1 || a==static_cast(filename.size())-1) { //Błąd plik nie posiada rozszerzenia assert(0); } w.assign(filename, a+1, filename.size()- a-1); //Zwracamy rozszerzenie zapisane tylko dużymi literami std::transform(w.begin(), w.end(), w.begin(), ::toupper); return w; } //Klasa reprezentująca dowolny obrazek. class Image { public: //Metoda zwracająca nam wskaźnik na konkretną instancje obiektu Image. virtual Image* Clone()=0; virtual string getName()=0; /* * W tym miejscu definiujemy metody obsługujące obrazek. Oczywiście są to także * metody wirtualne. */ }; /* * Klasa reprezentująca obrazek zapisany w formacie JPG. Jest to jednocześnie * implementacja wzorca prototyp. */ class ImageJPG : public Image { //Funkcja zwracająca nam wskaźnik na nowy obiekt klasy ImageJPG. Image* Clone() { return new ImageJPG(*this); } //Funkcja zwracająca srting z opisem klasy string getName() { return string("Klasa przetwarzająca pliki z rozszerzeniem JPG"); } }; //Podobnie jak klasa ImageJPG. class ImageGIF : public Image { Image* Clone() { return new ImageGIF(*this); } string getName() { return string("Klasa przetwarzająca pliki z rozszerzeniem GIF"); } }; /* * Klasa pomocnicza wiążąca prototyp klasy przetwarzającej obraz z odpowiednią wartością * pola IMG_TYPE. */ class ImagePrototype { public: ImagePrototype(IMG_TYPE t, Image *p):type(t),prototype(p){} IMG_TYPE type; Image *prototype; }; //Klasa, której używamy do tworzenia obiektów. class ImagePrototypeFactory { std::vector 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(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 */ } /* * Przy własnej implementacji tego wzorca, należy pamiętać o mechanizmie obsługi wyjątków, * który tutaj został pominięty. * Innym częstym przykładem użycia tego wzorca jest zapis stanu naszego programu do pliku, * wtedy podobnie jak w wyżej wymienionym przykładzie dla każdej klasy definiujemy * 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 * identyfikator do odpowiedniej funkcji fabryki prototypów(w pokazanym przeze mnie * przykładzie była to funkcja getImage()), która to funkcja zwraca nam wskazanie na nowo * utworzony obiekt klasy powiązanej z tym identyfikatorem. * Ważną cechą zaprezentowanego przeze mnie wzorca jest łatwość rozbudowy programu o dalsze klasy. * Nowo napisaną klasę(musi ona implementować wzorzec prototypu) rejestrujemy w naszej fabryce poprzez * wywołanie funkcji addPrototype. */