Adapter Design Pattern in C++
In diesem Artikel zeige ich euch das Adapter Design Pattern in C++ programmiert vor. Dieses Pattern wird vielen ein Begriff sein, denn nur durch einen Adapter können bestimmte Klassen interagieren, die normalerweise nicht kompatibel wären. Das ist fast immer notwendig, wenn man externe Bibliotheken einbindet oder mit externen Daten arbeitet.
Adapter Design Pattern in C++
Das Adapter Design Pattern gehört zu den Strukturpattern und hat zwei Anwendungsgebiete:
- Schnittstelle
Bildet ein Interface damit zwei Klassen kompatibel sind, welche aufgrund ihrer Interfaces nicht kompatibel wären. Ein einfaches Beispiel ist eine Schnittstelle, die Zahlenwerte liefert die einen . als Dezimalstelle haben und ein , als Tausender Trennzeichen haben. Diese Werte können nicht ohne weiteres in einem Berechnungsmodul verwendet werden, welches , als Dezimalstelle und . als Tausender Trennzeichen verwendet. In einem Adapter als Zwischenschritt würden wir die Zahlen konvertieren und erst danach an die jeweilige Klasse übergeben. - Kompatibilität
eine Klasse wird durch ein neues Interface kompatibel mit einem bestimmten System
Ich habe mich für meine Beispielimplementierung für den zweiten Schritt entschieden. Das ist recht oft dann nötig, wenn verschiedene Teams an unterschiedlichen Bereichen einer Software arbeiten und Schnittstellen entweder nicht klar definiert werden oder aber die Kommunikation mangelhaft ist. Den Source Code für dieses Beispiel findet man auf meiner GitHub Seite.
Beispiel
Die Entwicklung des Inventars für unser RGP wurde aus Kostengründen an ein externes Team out gesourced. Dieses hat in Monate langer Arbeit die CrappyArrayInventory Klasse erstellt mit den beiden Methoden getInventory(), welches einen Pointer auf das Item Array und getInventorySize(), welches die Anzahl an Items im Inventar zurückgibt:
class CrappyArrayInventory { public: CrappyArrayInventory(); Item* getInventory(); int getInventorySize(); private: Item* items; };
Leider hat das Core Entwicklerteam den Code für das Spiel so gebaut, dass bei jeder Abfrage des Inventars bzw. dessen Items getItem() bzw. getItemAmount() verwendet werden. Was nun?
- das externe Entwicklerteam bitten den das System mit neuer Schnittstelle anzupassen und eventuell mehrere Wochen Entwicklungszeit in kauf nehmen?
- alle Aufrufe in der Core Applikation neu schreiben und Zeit verlieren?
In diesem Fall hilft uns ein Adapter, der beide Anforderungen erfüllt und die Aufrufe korrekt abbildet. Diese Adapter Klasse nennen wir ModernInventory:
class ModernInventory { public: ModernInventory(); Item* getItem(int); const int getItemAmount() const; protected: CrappyArrayInventory* pCrap; };
wobei die beiden Methoden wie folgt implementiert wurden:
Item* ModernInventory::getItem(int index) { if (index > pCrap->getInventorySize()) return NULL; else return &pCrap->getInventory()[index]; } const int ModernInventory::getItemAmount() const { return pCrap->getInventorySize(); }
Dank dem Adapter Design Pattern reicht die Erstellung einer Adapter Klasse und im Core Source Code muss maximal der Klassennamen des Inventars getauscht werden.
Fazit
Das Beispiel zeigt sehr schön wie ein Adapter Design Pattern funktioniert. Mit Adaptern erspart man sich viel arbeit und kann recht einfach unterschiedlichste Interfaces über eine eigens definiere Schnittstelle ansprechen. Ein guter Adapter löst auch allerlei Probleme mit unterschiedlichen Datenformaten.