Adapter Muster
Inhaltsverzeichnis
Konzept
Das Adapter Muster ist, wie der Name schon sagt, ein Muster, welches als ein Adapter zwischen 2 Objekten fungiert. Diese Muster ist sehr nützlich bei dem flexiblen Programmieren von ressourcenabhängigen Managern.
Das Konzept des Adapters ist es, Daten, die in einen Format vorliegen, durch den Adapter in ein für die andere nutzbare Form zu bringen. So kann ein Adapter z.B. Bitmaps lesen und diese in das benötigte Format umwandeln, ein anderer Adapter kann dies für ein PNG und ein weiterer kann dies z.B. für eine AVI.
Umsetzung
Skizze
Wir haben gerade von mehren möglichen Adaptern geredet, die verschiedene Bildformate und Bildsequenzformate lesen sollen. Also sollten wir erstmal ermöglichen, dass man mehrere Adapter mit jeweils einen Dateiformat verbinden kann.
C++
class TAdapter { public: TAdapter(string FileName); }; typedef TAdapter* (*CreateAdapterInstanceCallback)(string FileName); class TAdapterManager { protected: map<string,CreateAdapterInstanceCallback> RegisteredAdapters; public: void RegisterAdapter(string ExtensionName, CreateAdapterInstanceCallback Callback); TAdapter* GetAdapterInstance(string ExtensionName); bool RemoveAdapter(string ExtensionName); };
Pascal
TAdapter = class public: constructor Create(FileName: String); end; (* Anmerkung: Objekt Pascal kennt die Möglichkeit class of class und diese kann man anstatt eines Callbacks verwenden. Nutzen tut man dieses dann so: type TAdapterCls = class of TAdapter; var AdapterCls: TAdapterCls; MyAdapter: TAdapter; begin MyAdapter := AdapterCls.Create(); end; *) CreateAdapterInstanceCallback = function (FileName: String): TAdapter; TAdapterManager = class protected: RegisteredAdapters: TUniqueHashList; //Eine Liste in der kein Wert doppelt vorkommt und die Strings durch Hashes im Zugriff optimiert werden. public: procedure RegisterAdapter(ExtensionName: String; Callback: CreateAdapterInstanceCallback); function GetAdapterInstance(ExtensionName: String): TAdapter: function RemoveAdapter(ExtensionName: String): Boolean; end;
Jetzt können wir einen eigenen Adapter dem Adaptermanager bekannt machen, indem wir die Extension(z.B. "gz" oder "avi") und ein passenden Callback übergeben. Der Callback besitzt nur eine Zeile Code, denn er erzeugt nur eine Instanz und gibt diese zurück.
Abstrahierung
Nun können wir Adapter registrieren, erstellen und entfernen, aber was ist mit dem Austausch von Daten?
Da der Adapter wissen muss, was der Empfänger für Daten haben will, muss der Adapter für den Empfänger zugeschnitten werden und der Empfänger braucht auch eine Routine, die die Daten vom Adapter entgegen nimmt und verarbeitet. Also müssen wir die Klassen möglichst abstrakt bauen und die Verarbeitung den Nachfahren überlassen.
C++
class TAdapter { public: virtual bool LoadFile(string FileName)=0; virtual void* GetData()=0; }; typedef TAdapter* (*CreateAdapterInstanceCallback)();//ruft den constructor auf und LoadFile wird gesondert aufgerufen
Pascal
TAdapter = class public: function LoadFile(FileName: String): Boolean; virtual; abstract; function GetData(): Pointer; virtual; abstract; end; CreateAdapterInstanceCallback = function: TAdapter; //ruft nur den Konstruktor auf, geladen wird erst nach der Übergabe der Instanz
Beispiel
Nun mal ein kleines Beispiel für die Einsetzung des Pattern:
Wir haben einen Adaptermanager für Konfigurationsdateien, ein Adapter für das Lesen von XML Dateien und eine Klasse, die die Konfiguration auswertet:
C++
class TAdapter { public: virtual bool LoadFile(string FileName)=0; virtual void* GetData()=0; }; struct TConfigData { unsigned int Resolution; }; class TConfigAdapterForXML:public TAdapter { protected: TConfigData* ConfigFile; public: bool LoadFile(string FileName){ ...lade und parse XML Datei... ConfigFile=new TConfigData; ConfigFile->Resolution=XMLParsedResolutionValue; ...Datei schliessen Parser freigeben... } void* GetData(){ return (void*)ConfigFile; } } typedef TAdapter* (*CreateAdapterInstanceCallback)(); TAdapter* MyConfigXMLAdapter(){ return new TConfigAdapterForXML; } class TAdapterManager { protected: map<string,CreateAdapterInstanceCallback> RegisteredAdapters; public: virtual void RegisterAdapter(string ExtensionName, CreateAdapterInstanceCallback Callback); virtual TAdapter* GetAdapterInstance(string ExtensionName); virtual bool RemoveAdapter(string ExtensionName); }; class TConfig{ protected: unsigned int Resolution; TAdapterManager* Manager; public: TConfig(TAdapterManager* AManager){ Manager=AManager; Resolution=0; } bool Load(){ TAdapter* Adapter=Manager->GetAdapterInstance(".xml"); if (Adapter) { if(Adapter->LoadFile("playerconfig.xml")) Resolution=(TConfigData*)(Adapter->GetData())->Resolution; else Resolution=DEFAULT_RESOLUTION; delete Adapter; } } unsigned int GetResolution(){ return Resolution; } }; int main(){ TAdapterManager* Manager=new TAdapterManager; Manager->RegisterAdapter(".xml",MyConfigXMLAdapter); TConfig* Config=new TConfig(Manager); cout<<Config->GetResolution()<<end;//0 Config.Load(); cout<<Config->GetResolution()<<end;//0 oder Wert aus der XML File. Manager->RemoveAdapter(".xml"); }
Pascal
TAdapter = class public: function LoadFile(FileName: String): Boolean; virtual; abstract; function GetData(): Pointer; virtual; abstract; end; PConfigData = ^TConfigData; TConfigData = packed record Resolution: Cardinal; end; TConfigAdapterForXML = class(TAdapter) protected: ConfigFile: PConfigData; public: function LoadFile(FileName: String): Boolean; overload; function GetData(): Pointer; overload; end; function TConfigAdapterForXML.LoadFile(FileName: String); begin //...lade und parse XML Datei... GetMem(ConfigFile, SizeOf(TConfigData)); ConfigFile.Resolution = XMLParsedResolutionValue; //...Datei schliessen Parser freigeben... end; function TConfigAdapterForXML.GetData(): Pointer; begin Result := ConfigFile; end; CreateAdapterInstanceCallback = function: TAdapter; //ruft nur den Konstruktor auf, geladen wird erst nach der Übergabe der Instanz function MyConfigXMLAdapter(): TAdapter; begin Result := TConfigAdapterForXML.Create(); end; TAdapterManager = class protected: RegisteredAdapters: TUniqueHashList; public: //procedure RegisterAdapter(ExtensionName: String; AdapterCls:TAdapterCls); procedure RegisterAdapter(ExtensionName: String; Callback: CreateAdapterInstanceCallback); function GetAdapterInstance(ExtensionName: String): TAdapter: function RemoveAdapter(ExtensionName: String): Boolean; end; TConfig = class protected: Resolution: Cardinal; Manager: TAdapterManager; public: constructor Create(AManager: TAdapterManager); function Load(): Boolean; function GetResolution(): Cardinal; end; constructor TConfig.Create(AManager: TAdapterManager); begin Manager := AManager; Resolution := 0; end; function TConfig.Load(); var Adapter: TAdapter; begin Adapter := Manager.GetAdapterInstance('.xml'); if Assigned(Adapter) then begin if Adapter.LoadFile('playerconfig.xml') then Resolution := TConfigData(Adapter.GetData()^).Resolution else Resolution := DEFAULT_RESOLUTION; Adapter.Free(); end; end; function TConfig.GetResolution(): Cardinal; begin Result := Resolution; end; var Manager: TAdapterManager; Config: TConfig; begin Manager := TAdapterManager.Create(); Manager.RegisterAdapter('.xml', MyConfigXMLAdapter); Config := TConfig.Create(Manager); Writeln(Config.GetResolution()); //0 Config.Load(); Writeln(Config.GetResolution()); //0 oder Wert aus der XML File. Manager.RemoveAdapter('.xml'); }
Fazit
Dies war ein genauerer Pseudocode, beide Versionen sind nicht getestet, aber man sollte den Sinn recht schnell verstehen können. Man könnte nun weitere Adapter für ini, txt, windows registry und so weiter schreiben, der Config-Klasse würde dies nichts ausmachen, da es ja seine gewollten Daten bekommt und verarbeiten kann.
Einsatzbeispiele wären Texturen, Sound, Models, Materials, Scripte und Shader. Mein TexturManager z.B. nutzt Adapter für DDS und der TexturAdapter ist so konzipiert, dass man sogar Bildsequenz basierte Formate (àla avi) davon ableiten könnte. Da man Adapter registrieren kann und sie nicht fest verdrahtet sind, kann man diese z.B. in Bibliotheken packen und so ein Pluginsystem realisieren. Ich selber rate allerdings von solch einem Pluginsystem ab, da es die Engine sehr unsicher macht und die Plugins Fehler mit in die Software bringen können.
Bei Fragen, Kritik, Verbesserungsvorschläge oder ähnlichem einfach mir (TAK2004) eine PM im Forum schreiben.