Das Prototype Pattern gehört zu den kreativen Entwurfsmustern und dient dazu, Objekte durch Klonen eines bestehenden Objekts zu erzeugen, anstatt sie von Grund auf neu zu instanziieren. Dieses Muster wird häufig verwendet, wenn die Erstellung eines neuen Objekts teuer oder komplex ist und es daher effizienter ist, ein bestehendes Objekt zu kopieren. Die Idee hinter dem Prototype Pattern ist, dass ein Objekt als „Prototyp“ dient und durch Kopieren dieses Prototyps neue Instanzen erzeugt werden.
Was ist das Prototype Pattern?
Im Gegensatz zu anderen Entwurfsmustern, die die Objektinstanziierung auf bestimmte Weise steuern, konzentriert sich das Prototype Pattern auf das Kopieren eines Objekts. Ein Prototyp ist dabei ein Vorlagenobjekt, das alle notwendigen Daten und Strukturen für das zu erstellende Objekt enthält. Anstatt jedes Mal ein neues Objekt mit einem Konstruktor zu erstellen, wird eine Kopie des bestehenden Prototyps erzeugt. Dies ist insbesondere dann vorteilhaft, wenn das Erstellen eines Objekts aufwändig ist.
Das Prototype Pattern bietet eine einfache Möglichkeit, ein neues Objekt zu erzeugen, indem das bestehende Objekt einfach kopiert wird. Dies wird insbesondere dann nützlich, wenn das Erstellen von Objekten mit komplexen Initialisierungen oder langwierigen Berechnungen verbunden ist.
Grundstruktur des Prototype Patterns
Die Struktur des Prototyp Patterns besteht aus mehreren wichtigen Komponenten:
- Prototype: Eine abstrakte Klasse oder Schnittstelle, die die Methode
clone()
deklariert. Diese Methode wird verwendet, um eine exakte Kopie des Objekts zu erstellen. - ConcretePrototype: Eine konkrete Implementierung der
Prototype
-Schnittstelle, die die Methodeclone()
überlädt und dafür sorgt, dass eine exakte Kopie des Objekts erstellt wird. - Client: Der Client-Code, der die
clone()
-Methode verwendet, um neue Instanzen eines Objekts zu erstellen, indem er das Prototypobjekt kopiert.
Beispiel des Prototype Patterns in C++
Das folgende C++-Beispiel veranschaulicht die Funktionsweise des Prototyp Patterns. Wir modellieren ein einfaches Szenario, in dem verschiedene Arten von „Karten“ als Objekte existieren. Jede Karte hat bestimmte Eigenschaften, und durch das Prototyp Pattern können wir neue Karten erzeugen, indem wir bestehende Karten kopieren.
#include <iostream>
#include <string>
// Prototype Interface: Deklariert die clone() Methode
class CardPrototype {
public:
virtual CardPrototype* clone() const = 0;
virtual void display() const = 0;
virtual ~CardPrototype() = default;
};
// ConcretePrototype: Eine konkrete Implementierung der Karte
class CreditCard : public CardPrototype {
private:
std::string cardNumber;
std::string cardHolder;
std::string expirationDate;
public:
CreditCard(const std::string& number, const std::string& holder, const std::string& expiration)
: cardNumber(number), cardHolder(holder), expirationDate(expiration) {}
// Implementierung der clone-Methode
CreditCard* clone() const override {
return new CreditCard(*this); // Klonen der Karte
}
void display() const override {
std::cout << "Kreditkarte:\n"
<< "Nummer: " << cardNumber << "\n"
<< "Inhaber: " << cardHolder << "\n"
<< "Ablaufdatum: " << expirationDate << "\n";
}
};
// ConcretePrototype: Eine andere konkrete Implementierung einer Karte
class DebitCard : public CardPrototype {
private:
std::string cardNumber;
std::string cardHolder;
std::string bankName;
public:
DebitCard(const std::string& number, const std::string& holder, const std::string& bank)
: cardNumber(number), cardHolder(holder), bankName(bank) {}
// Implementierung der clone-Methode
DebitCard* clone() const override {
return new DebitCard(*this); // Klonen der Debitkarte
}
void display() const override {
std::cout << "Debitkarte:\n"
<< "Nummer: " << cardNumber << "\n"
<< "Inhaber: " << cardHolder << "\n"
<< "Bank: " << bankName << "\n";
}
};
// Client-Code
int main() {
// Erstellen einer Kreditkarte und einer Debitkarte
CreditCard* originalCreditCard = new CreditCard("1234 5678 9876 5432", "Max Mustermann", "12/25");
DebitCard* originalDebitCard = new DebitCard("9876 5432 1234 5678", "Julia Schmidt", "Sparkasse");
// Klonen der Kreditkarte und der Debitkarte
CreditCard* clonedCreditCard = static_cast<CreditCard*>(originalCreditCard->clone());
DebitCard* clonedDebitCard = static_cast<DebitCard*>(originalDebitCard->clone());
// Anzeigen der Originale und der Klone
std::cout << "Original Kreditkarte:\n";
originalCreditCard->display();
std::cout << "\nGeklonte Kreditkarte:\n";
clonedCreditCard->display();
std::cout << "\nOriginal Debitkarte:\n";
originalDebitCard->display();
std::cout << "\nGeklonte Debitkarte:\n";
clonedDebitCard->display();
// Aufräumen
delete originalCreditCard;
delete originalDebitCard;
delete clonedCreditCard;
delete clonedDebitCard;
return 0;
}
Erklärung des C++-Beispiels
Im obigen Beispiel haben wir die Prototype-Schnittstelle, die die clone()
-Methode deklariert. Die Methode clone()
ist dafür verantwortlich, eine exakte Kopie des Objekts zu erstellen. Es gibt zwei konkrete Implementierungen des Prototyps:
- CreditCard: Diese Klasse stellt eine Kreditkarte dar. Sie implementiert die Methode
clone()
, die eine Kopie des aktuellen Objekts zurückgibt. - DebitCard: Diese Klasse stellt eine Debitkarte dar, die ebenfalls die Methode
clone()
implementiert.
Im Client-Code werden zunächst zwei Karten erstellt: eine CreditCard
und eine DebitCard
. Anschließend werden diese Karten geklont, und die Details der Original- und der geklonten Karten werden ausgegeben.
Die Methode clone()
in beiden Klassen ermöglicht es, schnell Kopien der bestehenden Karten zu erstellen, ohne sie manuell zu rekonstruieren. Dies spart sowohl Zeit als auch Ressourcen, insbesondere wenn die Erzeugung der Objekte komplex oder teuer ist.
Vorteile des Prototype Patterns
- Effizienz: Wenn das Erstellen eines Objekts ressourcenintensiv oder zeitaufwendig ist, kann das Kopieren eines bestehenden Objekts viel schneller und effizienter sein. Besonders dann, wenn nur wenige Unterschiede zwischen den Objekten bestehen.
- Flexibilität: Das Prototyp Pattern ermöglicht es, Objekte zur Laufzeit zu klonen und anzupassen. Dadurch können flexibel neue Instanzen erzeugt werden, ohne den gesamten Konstruktionsprozess neu zu durchlaufen.
- Einfachheit: Im Vergleich zu anderen Entwurfsmustern, bei denen der Client eine komplexe Objektinstanziierung durchlaufen muss, vereinfacht das Prototyp Pattern den Prozess, indem es den Klonvorgang zentralisiert und standardisiert.
- Reduzierung der Code-Duplikation: Da das Klonen eines Objekts im Wesentlichen nur das Kopieren der Daten eines bestehenden Objekts bedeutet, wird der Code zur Erstellung neuer Objekte reduziert, was zu weniger Duplikation und damit zu saubererem Code führt.
Nachteile des Prototype Patterns
- Komplexe Objekte: Wenn ein Objekt sehr komplex ist, kann das Klonen schwierig sein, insbesondere wenn es tiefe Kopien von Objekten oder komplexe Abhängigkeiten erfordert. Das Kopieren von Referenzen statt tiefem Kopieren kann zu unerwarteten Problemen führen.
- Abhängigkeiten zwischen Objekten: Wenn Objekte enge Beziehungen zueinander haben, kann das Klonen dazu führen, dass ungewollte Verknüpfungen zwischen den Kopien entstehen, was zu unvorhersehbaren Verhaltensweisen führen kann.
Fazit
Das Prototype Pattern ist ein wertvolles Entwurfsmuster, wenn es darum geht, Objekte effizient zu erstellen, indem bestehende Instanzen kopiert werden. Besonders in Szenarien, in denen das Erstellen von Objekten ressourcenintensiv ist , kann das Prototyp Pattern die Effizienz und Flexibilität steigern. In C++ lässt sich das Muster elegant implementieren, indem eine clone()
-Methode verwendet wird, die es ermöglicht, bestehende Objekte zu duplizieren und bei Bedarf anzupassen.
Zu der Liste der Design-Pattern: Liste der Design-Pattern