Fallen JLI MVP
Alter: 40 Anmeldedatum: 08.03.2003 Beiträge: 2860 Wohnort: Münster Medaillen: 1 (mehr...)
|
Verfasst am: 08.11.2006, 14:29 Titel: Interfaces |
|
|
Was ist ein Interface?
Interfaces sind Beschreibungen für das Aussehen der tatsächlichen Implementation der Klassen, im Gegensatz zu abstrakten Klassen werden bei Interfaces keine Funktionen implementiert (bis auf Destruktoren, dazu aber später mehr).
Wozu dient das ganze?
Interfaces werden dazu benutzt eine gemeinesame Schnittstelle und ein wohldefiniertes Konstrukt der Klassenstrukturen anzubieten. So wird gewährleistet das spätere Implementationen problemlos ausgetauscht werden können, durch Plugins oder einfach neuere Versionen der alten Klassen.
Was sind die Besonderheiten?
Das besondere an Interfaces ist das die Implementationen nur ein einziges mal Speicher für die Funktionen anfordern müssen, was bei Objekten wie zB Gegner/Items/Partikel einen enormen Speichervorteil darstellen kann, je nachdem wie viele Methoden man nun benutzt.
Noch dazu wird die benutzung der GUIDs enorm vereinfacht. Die Übersichtlichkeit wird erhöhrt und für den Klienten bleiben die tatsächlichen Implementierungen und die dazugehörigen Verwirrungen verborgen da der Endklient nur die Interfaces benutzten soll.
Eine wichtige besonderheit von Interfaces ist übrigens auch das virtuelle Destruktoren schon im Interface implementiert werden müssen da ansonsten eine Fehlermeldund die weitere benutzung unterbindet. Virtuelle Destruktoren haben den Vorteil, wenn kontinuierlich genutzt, das auch Ableitungen den Destruktor der unterliegenden Klassen automatisch aufrufen wenn sie zerstört werden.
Hilfsfunktionen/Klassen:
Man kann sich das verwenden der Interfaces enorm vereinfachen und damit auch die Benutzung der Implementierungen. Dazu bietet zumindest der VC Compiler nützliche Schlüsselwörter an, welche man in Kombination mit geschickten Code wunderbar weiter verwenden kann.
Dazu gehört zB das casten in eine bestimmte andere Klasse ohne die Verwendung von Strings, Flags oder anderen Identifizierungsmitteln innerhalb der Klasse selbst durch zB Attribute.
Anwendungsbeispiel:
Hier werde ich nun ein kleines Beispiel vorzeigen für die benutzung von Interfaces. Als erstes ein sehr simples Interface:
CPP: | struct __declspec(novtable) __declspec(uuid("{EDFDE891-F04D-4061-8CCC-F95959C16354}")) iBaseObject
{
// virtueller Destructor wird implementiert!
virtual ~iBaseObject {}
// ales andere nicht
virtual void* getInterface(const GUID& InterfaceGuid) = 0;
}; |
Wie man gut sehen kann habe ich bei der Struktur von iBaseObject 2 Schlüsselwörter benutzt, zum einen __declspec(novtable) was bedeutet das für diese Struktur allein kein VTable erstellt werden soll, was ja auch ziemlich unsinnig wäre da diese Struktur so nie verwendet wird (es kann ja auch nichts).
Zum anderen habe ich __declspec(uuid("{EDFDE891-F04D-4061-8CCC-F95959C16354}")) verwendet, womit ich diesem Interface eine eindeutige/einmalige Kennzeichnung (GUID = Globally Unique Identifier) verpasse. Mit dieser GUID lässt sich eine spätere Klasse testen ob sie etwas mit dem interface gemeinsam hat, dazu später mehr.
iBaseObject wird später die für alle weiteren Interfaces das zu grunde liegende StandardInterface bilden. Es ist zwar nicht immer nötig, aber in diesem tutorial werde ich es verwenden und auch gleich erklären weshalb.
Nun zeige ich eine Beispiel BaseEntity Interfacebeschreibung:
CPP: | struct __declspec(novtable) __declspec(uuid("{18ED9EBE-E346-4762-BA22-A3EE7EC352AD}")) iBaseEntity
: public iBaseObject
{
// virtueller Destructor wird implementiert!
virtual ~iBaseEntity {}
// nuetzliche Methoden
virtual std::string getName() const = 0;
virtual void setPosition(const Vector3& Position) = 0
virtual Vector3 getPosition() const = 0
...
}; |
Zu beachten ist hier das iBaseEntity eine neue GUID verpasst bekommen hat, erstellen lassen sich GUIDs unter VisualStudio übrigens sehr einfach über den Menüpunkt "Extras->GUID erstellen..." einfach auf "Copy" beim GUID Generator klicken und im Code einsetzen. Schön einfach das ganze.
Nun werde ich nochmal auf die Bedeutung der getInterface Methode etwas erläutern dazu aber ein Anwendungsbeispiel.
Angenommen wir haben noch weitere Interfaces:
CPP: | struct __declspec(novtable) __declspec(uuid("{9626799C-FF45-4517-AB36-B4162D71A087}")) iPlayerEntity
: public iBaseObject
{
// virtueller Destructor wird implementiert!
virtual ~iPlayerEntity {}
// nuetzliche Methoden
...
};
struct __declspec(novtable) __declspec(uuid("{9626799C-FF45-4517-AB36-B4162D71A087}")) iItemEntity
: public iBaseObject
{
// virtueller Destructor wird implementiert!
virtual ~iItemEntity {}
// nuetzliche Methoden
...
}; |
und deren Implementation:
CPP: | class cBaseEntity
: public iBAseEntity // und unser interface als Schablone benutzen
{
public:
...
virtual void* getInterface(const GUID& InterfaceGuid);
...
};
class cPlayerEntity
: public cBaseEntity // Basisimplementation benutzen \o/
, public iPlayerEntity // und unser interface als Schablone benutzen
{
public:
...
virtual void* getInterface(const GUID& InterfaceGuid);
...
};
class cItemEntity
: public cBaseEntity // Basisimplementation benutzen \o/
, public iItemEntity // und unser interface als Schablone benutzen
{
public:
...
virtual void* getInterface(const GUID& InterfaceGuid);
...
}; |
getInterface wird benötigt um die Klasse wenn möglich in ein gewünschtes Interface zu bringen, was wir dann dazu benutzen können um heraus zu finden welchem Typ die Klasse angehört:
CPP: | void* cBaseEntity::getInterface(const GUID& InterfaceGuid)
{
if (InterfaceGuid == __uuidof(iBaseEntity))
return static_cast<iBaseEntity*>(this);
// nichts liegt hier drunter
return NULL;
}
void* cPlayerEntity::getInterface(const GUID& InterfaceGuid)
{
if (InterfaceGuid == __uuidof(iPlayerEntity))
return static_cast<iPlayerEntity*>(this);
// Basis abfragen
return cBaseEntity::getInterface(InterfaceGuid);
}
void* cItemEntity::getInterface(const GUID& InterfaceGuid)
{
if (InterfaceGuid == __uuidof(iItemEntity))
return static_cast<iItemEntity*>(this);
// Basis abfragen
return cBaseEntity::getInterface(InterfaceGuid);
} |
Wenn man dies dann im Code benutzt braucht man nicht auf gewöhnliche Weise einen Flag abfragen der erst umständlich definiert und gesetzt werden muss, sondern braucht nur überprüfen ob der Rückgabewert von getInterface ungleich oder gleich NULL ist:
CPP: | iBaseEntity* entity = EntityCreator->CreateHealthpack();
// position setzen
entity->setPosition(Vector3(10, 5, 0));
// heilstaerke setzen
iHealthEntity* healthpack = entity->getInterface(__uuidof(iHealthEntity));
if (!healthpack)
Error("entity ist kein HealthPack!");
healthpack->setHealrate(100.0f); |
Da das ganze recht unschön aussieht gibt es dagegen auch gute Möglichkeiten dieses zu vereinfachen durch eine simple template Methode:
CPP: | template<typename TargetType> TargetType* interface_cast(iBaseObject* sourceObject)
{
if (!sourceObject)
return NULL;
return reinterpret_cast<TargetType*> ( sourceObject->getInterface(__uuidof(TargetType)) );
}; |
Wodurch das Beispiel von oben nun so aussehen könnte:
CPP: | iBaseEntity* entity = EntityCreator->CreateHealthpack();
// position setzen
entity->setPosition(Vector3(10, 5, 0));
// heilstaerke setzen
iHealthEntity* healthpack = interface_cast<iHealthEntity>(entity); // beinahe so wie static_cast oder ähnliches =)
if (!healthpack)
Error("entity ist kein HealthPack!");
healthpack->setHealrate(100.0f); |
Da ich grade verwirrt bin und sowas immer direkt im Forum bin hoffe ich auf unterstützende Kritik, Meinung, Kommentare, Liebes sowie Hassbriefe.
mfg Mark
PS: Sollte GUID ein unbekannter Typbezeichner sein so nutzt einfach das hier:
CPP: | #include <guiddef.h> |
_________________ "I have a Core2Quad at 3.2GHz, 4GB of RAM at 1066 and an Nvidia 8800 GTS 512 on Vista64 and this game runs like ass whereas everything else I own runs like melted butter over a smokin' hot 18 year old catholic schoolgirl's arse." |
|