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
Prześlij komentarz