Factory Design Pattern in C++
Das Factory Design Pattern ist sehr einfach zu verstehen und zu implementieren und bildet den Startpunkt meiner Design Pattern Artikelreihe. Ich zeige euch in jedem Artikel worum es bei dem Design Pattern geht und wie man dieses anhand eines praktischen Beispiels einsetzt.
Factory Design Pattern in C++
Beim Factory Design Pattern geht es darum Objekte zu erzeugen ohne die dazugehörige Klasse zu kennen. Die Erzeugung der Objekte wird durch eine Factory Klasse abstrahiert, das heißt bei der Implementierung und Verwendung der Objekte muss man nur noch eine Factory Klasse verwenden. Wie diese einzelnen speziellen Objekte erzeugt werden und welche Konstruktoren diese besitzen ist nicht von belang. Will man also bestimmte Klassen verwenden reicht das Wissen über die korrekte Erzeugung mit der zugehörigen Factory Klasse und die Verwendung der Klasse über ihre Methoden. Alles weitere kann beispielsweise verborgen in einer Bibliothek implementiert sein.
Für das Factory Design Pattern benötigen wir also eine Basisklasse von der spezialisierte Klassen erben. Hat man so ein Konstrukt, reicht die Implementierung einer Factory Klasse und wir können dieses Design Pattern bereits anwenden. Aufgrund der einfachen Umsetzung ist es eines der am häufigsten verwendeten Design Pattern überhaupt.
Praxisbeispiel
Für ein Rollenspiel benötigt man zahlreiche unterschiedliche Charaktere. Dafür gibt es eine Basisklasse Character. Damit das Beispiel nicht zu umfangreich wird hat unser Charakter zwei Eigenschaften:
- name
jeder Charakter in diesem Spiel hat einen eindeutigen Namen - damage
jeder Charakter kann Schaden in fest gelegter Höhe machen
Zusätzlich dazu hat jeder Charakter eine Aktion in der er einen Angriff (attack) machen kann. Diese Attack Methode wird jede einzelne spezialisierte Klasse anders implementieren, deshalb wird sie in der Basisklasse virtual deklariert.
Wir starten mit 3 unterschiedlichen Charakteren:
- Player
der Spieler - Orc
ein einfacher Gegner - Dragon
ein Boss Gegner
In der CharacterFactory wird eine create Methode implementiert. Über diese können wir nun zur Laufzeit des Programms durch Angabe eines Parameters beliebige Charaktere erzeugen. Das gesamte Wissen darüber wie spezielle Charaktere erzeugt werden müssen ist in dieser Methode implementiert.
Implementierung
Der Source Code für mein Beispiel ist wie immer auf GitHub zu finden. Hier einige wichtige Auszüge:
class CharacterFactory { public: // Factory Method static Character* create(int type); };
Basis des Patterns ist die CharacterFactory mit der dort implementierten create Methode. Diese ist static, kann also ohne Instanz der Klasse aufgerufen werden. Man erhält ein Objekt vom Typ der Basisklasse Character. Durch diesen Pointer kann man auch jegliche Spezialklasse (Player, Orc oder Dragon) als Objekt zurück erhalten (genau genommen nur den Pointer auf die Adresse im Speicher). Die Methode wurde so implementiert:
Character* CharacterFactory::create(int type) { Character *character = NULL; switch(type) { case 1:{ character = new Player; character->setName("Gandalf"); character->setDamage(10.0); break; } case 2:{ character = new Orc; character->setName("Urbul"); character->setDamage(5.0); break; } case 3:{ character = new Dragon; character->setName("Bymarth, The Deathlord"); character->setDamage(500.0); break; } default:{ return NULL; } } return character; }
In diesem Fall werden in einem switch Konstrukt je nach übergebenem Wert die Spezialklassen erzeugt. In diesem Fall werden diese mit fixen Werten angelegt. Das ist nicht optimal und sollte im produktiven Einsatz korrigiert werden.
Zuletzt noch kurz die main Funktion als Einsprungspunkt des Programms.
int main(int argc, char **argv) { Character *player = CharacterFactory::create(1); Character *orc = CharacterFactory::create(2); Character *dragon = CharacterFactory::create(3); orc->attack(player); player->attack(orc); dragon->attack(player); return 0; }
Wir erzeugen je eine Objektinstanz (siehe den Aufruf CharacterFactory::create ohne Objektinstanz von CharacterFactory, weil die Methode static ist). Danach lassen wir diese gegeneinander kämpfen -> es wird eine Ausgabe erzeugt die zeigt, dass jeweils die korrekte attack() Funktion aufgerufen wird.
Fazit
Das Beispiel zeigt recht anschaulich wie einfach man das Factory Design Pattern implementieren kann. Es kann recht einfach auch in Anwendungen integriert werden, die darauf noch nicht basieren. Der C++ Source Code kann einfach in andere Sprachen umgesetzt werden, besonders in Java wird das Pattern sehr gerne verwendet.
Mal ne doofe Frage:
Über was schreibst du hier eigentlich? Ist das ein Programm, das man sich irgendwo herunterladen muss?
Ich programmiere schon eine Weile C++, aber von Factory Pattern habe ich noch nie gehört.
ich schreibe über Design Patterns. Den Beispielcode bekommst du von meiner GitHub Seite, die ist in jedem Artikel verlinkt. Das Factory Pattern ist wohl neuer als so manches andere. Ich kenne es seitdem ich mit Magento 2 arbeite, da bekommt man alle Objekte über Factories…
Und was ist ein Design Pattern?
So ganz hat deine Antwort meine Frage nicht beantwortet.
Ein Design Pattern bzw. zu Deutsch ein Entwurfsmuster ist ein quasi Standard wie man ein bestimmtes Problem im Source Code optimal darstellt. Jeder gute Programmierer kennt Design Pattern und kann diese am besten im Schlaf in und auswendig anwenden bzw. in bestehenden Code finden.
Neuer? Sowohl das Factory Pattern als auch das Abstract Factory Pattern ist im originalen GoF Buch zu Pattern von 1994 drin….
Ich weis ich schon ne Weile her seit jemand da ein Kommentar geschrieben hast und vielleicht hast du das Projekt gar nicht mehr aber kannst du mir sagen wie der Code einer deiner Spezifischen Objekte aussieht? bei mir jammert VS das mein Spezifisches Objekt Abstrakt sei und ich bin mir nicht sicher was ich ändern muss
Hey, im Artikel hab ich den Source Code dazu auf GitHub verlinkt. Hilft dir das weiter?
Bin grad nur durch Zufall drauf gestoßen, aber da der Code ein paar potentielle Fehler enthält mal ein paar konstruktive Anmerkungen:
In Charakter.h wird damage nicht initialisiert, das kann dazu führen das einem das Program um die Ohren fliegt (floating point exception).
Alles was mit new allokiert wird sollte auch wieder mit delete gelöscht werden.
Am besten gleich Smart Pointer wie unique_ptr oder shared_ptr verwenden. Mit dem RAII Idiom und make_shared/make_unique und Konstruktorargumenten kann man das auch gleich in einer Zeile konstruieren und dafür sorgen das der Code sicher ist.
JEDE Klasse die Virtuelle Methoden deklariert sollte auch nen virtuellen Destruktor deklarieren.
NULL ist deprecated, statt dessen sollte nullptr verwendet werden.
Danke fürs Feedback. Der Code ist nur ein Showcase fürs Pattern und kein Produktivcode. Da er Open Source ist kannst deine Anmerkungen gerne auf GitHub für die Community hinzufügen, würde mich freuen.