Przejdź do głównej zawartości

Fabryka

Witam, dawno mnie tu nie było i nie mam dobrego usprawiedliwienia tego stanu rzeczy. W tym artykule przeanalizujemy wzorzec projektowy fabryka (factory, wirtualny konstruktor, metoda wytwórcza). Sprawa jest dość prosta wzorzec ten opisuje sytuacje w której proces tworzenia obiektu delegowany jest do specjalnej klasy(functora lub zwykłej funkcji lub singletona). Korzyść z takiego podejścia to scentralizowanie tworzenia skomplikowanych obiektów będących podklasami jednego ogólnego obiektu. Poniżej uogólniona implementacja metody wytwórczej:

template <typename T>
class Factory
{
public:
  static Factory<T>& getInstance()
  {
    static Factory<T>* instance = NULL;
    if(!instance)
    {
      std::lock_guard<std::mutex> guard( Factory<T>::factoryCreationMutex );
      if(!instance)
      {
        instance = new Factory<T>();
      }
    }
    return *instance;
  }

  T* createObject(std::string className)
    {return creationList[className]();}

  bool addObjectToCreationList(std::string className, std::function<T*(void)> creationFunction)
  {
    if( !creationList.count(className) )
    {
      creationList[className] = creationFunction;
      return true;
    }
    return false;
  }

private:
  std::map<std::string, std::function<T*(void)> > creationList;
  static std::mutex factoryCreationMutex;
};

template<typename T>
std::mutex Factory<T>::factoryCreationMutex;

Jak łatwo zauważyć prezentowana implementacja jest szablonowym singletonem. Na szczególną uwagę zasługuje metoda getInstance(). Stosuje ona kilka przebiegłych rozwiązań. Pierwszym z nich jest przechowywanie statycznego wskaźnika do obiektu wewnątrz własnego ciała co uniemożliwia dostęp do wskaźnika z poziomu innych metod.
Drugim sprytnym rozwiązaniem jest zastosowanie rozwiązania „double checked locking”, polega ono na założeniu mutexa dopiero po zweryfikowaniu czy nasz wyznacznik blokady (wartość wskaźnika instance) spełnia warunek jej założenia. Ze względu na niedeterministyczne zachowanie mechanizmu wywłaszczanie wątków, po założeniu blokady musimy ponownie zweryfikować badany warunek a następnie wykonać odpowiednie instrukcje. Takie podejście pozwala minimalizacje operacji założeń i zwolnień muteksa co w efekcie przyśpiesza działanie metody.
Metoda addObjectToCreationList() pozwala na dodanie identyfikatora tworzonej klasy oraz wskaźnika do funkcji tworzącej konkretny obiekt do listy w celu późniejszego wykorzystania. Jako identyfikator proponuje wykorzystać nazwę klasy co powinno zagwarantować jego unikalność.
Do tworzenia nowych obiektów wykorzystujemy metodę createObject() jako parametr podając wcześniej zarejestrowany identyfikator.

Klasycznym zastosowaniem wirtualnego konstruktora jest proces tworzenie różnych obiektów bazujących na tym samym interfejsie, typowym przykładem takiej hierarchii klas jest klasa opisująca zadany kształt, oraz jej konkretne implementacje.

class Shape
{
  public:
  virtual void createShapeFromDescription(std::string description) = 0;
  virtual void draw() = 0;
  virtual std::string getClassType(){return "Shape";}
  virtual ~Shape() {};
};

Omawianą specjalizacją klasy Shape jest prezentowana poniżej klasa Rect:

class Rect : public Shape
{
public:
  virtual ~Rect(){};
  virtual void createShapeFromDescription(std::string description)
    {/*implementatino is not important*/}
  virtual void draw()
    {/*implementatino is not important*/}
  virtual std::string getClassType(){return "Rect";}

  static Shape* createRect(){return new Rect();}
};

Metoda pozwalająca na utworzenie obiektu została umieszczona jako statyczna metoda klasy createRect(). Prezentowana implementacja wymaga jawnego miejsca w kodzie w którym zarejestrujemy dany typ obiektu w naszej fabryce. Poniższe 2 linijki powodują że klasa Rect zostanie zarejestrowana niemal automatycznie:

namespace
{
  const bool addRect =    Factory<Shape>::getInstance().addObjectToCreationList( "Rect", Rect::createRect );
}

Zmienne typu const muszą zostać zainicjalizowane jako pierwsze, co oznacza że rejestracja obiektów w fabryce nastąpi jeszcze przed rozpoczęciem funkcji main().

I w ten oto sposób została zaprezentowana uniwersalna metoda wytwórcza wraz z niezwykle eleganckim procesem rejestracji obiektów.  

Komentarze

Popularne posty z tego bloga

Makra i preprocesor

Jako programista klasycznego C przyszło mi wielokrotnie ścierać się z makrami. Makro to zestaw instrukcji umieszczanych w kodzie są jednak interpretowane nie przez kompilator ale przez preprocesor. Preprocesor jest „pomocnikiem” kompilatora, zajmuje się on np. wstawianiem treści plików nagłówkowych do plików z kodem za pomocą instrukcji #include. Preprocesor należy rozumieć jako prymitywny edytor tekstu dokonujący „korekcji” plików z kodem źródłowym przed rozpoczęciem ich przetwarzania przez kompilator. Jakie są zalety wykorzystywania tych rozwiązań w kodzie? Tak naprawdę w języku C w czasach przed wprowadzeniem słowa kluczowego inline umożliwiały wstawianie kodu we wskazane miejsce. Należy bowiem pamiętać że każdorazowe wstawienie makroinstrukcji powoduje ingerencję w kod źródłowy (innymi słowy we wskazanym miejscu zostanie wstawiony stosowny fragment kodu). Łatwo obserwowalnym efektem częstego wykorzystywania makr w plikach z kodem jest rozrost pliku binarnego oraz jego szybsze d

C++11 Iterowanie

Zasadniczo iterowanie po elementach tablicy/vectora/listy itp. nie jest niczym nowym, interesującym ani pasjonującym, ot szara codzienność. Przyjrzyjmy się zatem jak robimy to najczęściej: class Image { public:   Image();   void rotate(float angle);   void display();   void* serialize(); }; class ShowImage { public:   void operator()(Image image)   {     image.display();   } }; //... std::vector<Image> imageCollection(10); for(int i=0; i<imageCollection.size(); ++i) {   imageCollection[i].display(); } for( std::vector<Image>::iterator                          currentImage = imageCollection.begin();                         currentImage < imageCollection.end();                         ++currentImage ) {   currentImage->display(); } std::for_each(imageCollection.begin(), imageCollection.end(),               ShowImage()); Dla naszych potrzeb stworzyliśmy sp

C++11 Variadic templates – szablony ze zmienną liczba parametrów

Witam w nowym roku. Dziś będzie o nowym elemencie szablonów czyli o szablonach ze zmienną liczbą parametrów. Ich implementacja i zachowanie różni się nieco od klasycznych szablonów. Nowa funkcjonalność pozwala na tworzenie bezpiecznych list typów. template<typename ...Ts> void variadic_template(){} Pierwszą nowością jest zastosowanie ...(wielokropka) przy określaniu typów szablonu. W ten sposób sygnalizujemy mnogość typów. Niestety nie mamy możliwości iterowania po kolejnych typach wewnątrz naszego szablonu. Ponadto taka definicja pozwala na stworzenie/wywołanie naszej szablonowej funkcji bez typów. variadic_template<>(); Aby tego uniknąć można uciec się do następującej sztuczki: template<typename T1, typename ...Ts> void variadic_template(T1 arg, Ts... args) W powyższym przykładzie jawnie wymuszamy podanie przynajmniej jednego typ dla naszej funkcji szablonowej. Jak już wspomniałem nie możemy jawnie iterować po typach przekazanych do