JLI Spieleprogrammierung Foren-Übersicht JLI Spieleprogrammierung

 
 FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   BenutzergruppenBenutzergruppen 
 medals.php?sid=b2bd14501c5c44208a1faa2e77bd4fd5Medaillen   RegistrierenRegistrieren   ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin 

Interfaces

 
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Tutorials
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
Fallen
JLI MVP
JLI MVP


Alter: 40
Anmeldedatum: 08.03.2003
Beiträge: 2860
Wohnort: Münster
Medaillen: 1 (mehr...)

BeitragVerfasst am: 08.11.2006, 14:29    Titel: Interfaces Antworten mit Zitat

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."
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden Website dieses Benutzers besuchen
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Tutorials Alle Zeiten sind GMT
Seite 1 von 1

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.


Powered by phpBB © 2001, 2005 phpBB Group
Deutsche Übersetzung von phpBB.de

Impressum