Tutorials - Interfaces Tutorial

Sprachenübersicht/Programmierung/C / C++/ C#/OOP

Interfaces Tutorial

Diese Seite wurde 4496 mal aufgerufen.

Dieser Artikel wurde in einem Wikiweb System geschrieben, das heißt, Sie können die Artikel jederzeit editieren, wenn Sie einen Fehler gefunden haben, oder etwas hinzufügen wollen.

Editieren Versionen Linkpartnerschaft Bottom Printversion

Keywords: Interfaces

Inhaltsverzeichnis



Was ist ein Interface? Top



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? Top



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? Top



Das besondere an Interfaces ist das die Implementationen nur ein einziges mal Speicher für die Funktionen anfordern müssen, was bei Objekten wie z.B. Gegnern/Items/Partikeln 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, dass virtuelle Destruktoren schon im Interface implementiert werden müssen da ansonsten eine Fehlermeldung die weitere Benutzung unterbindet. Virtuelle Destruktoren haben den Vorteil, dass beikontinuierlicher benutzung auch Ableitungen den Destruktor der unterliegenden Klassen automatisch aufrufen, wenn sie zerstört werden.

Hilfsfunktionen/Klassen: Top



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 z.B. das Casten in eine bestimmte andere Klasse ohne die Verwendung von Strings, Flags oder anderen Identifizierungsmitteln innerhalb der Klasse selbst, z.B. durch Attribute.

Anwendungsbeispiel: Top



Hier werde ich nun ein kleines Beispiel für die Benutzung von Interfaces zeigen. Als erstes ein sehr simples Interface:

Code:


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 zwei Schlüsselwörter benutzt: Zum einen __declspec(novtable) was bedeutet, dass für diese Struktur allein kein VTable erstellt werden soll, was ja auch ziemlich unsinnig wäre da, diese Struktur so nie verwendet wird (sie kann ja auch nichts).

Zum anderen habe ich __declspec(uuid("{EDFDE891-F04D-4061-8CCC-F95959C16354}")) verwendet, womit ich diesem Interface eine eindeutige Kennzeichnung (GUID = Globally Unique Identifier) verpasse. Mit dieser GUID lässt sich für eine spätere Klasse testen, ob sie etwas mit dem interface gemeinsam hat, dazu später mehr.

iBaseObject wird später das für alle weiteren Interfaces zugrunde liegende StandardInterface bilden. Das 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:

Code:


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, dass iBaseEntity eine neue GUID verpasst bekommen hat, da es sich ja um ein anderes Interface handelt. 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.

Nun werde ich auf die Bedeutung der getInterface() Methode etwas näher eingehen. Dazu aber ein Anwendungsbeispiel.
Angenommen, wir haben noch weitere Interfaces:

Code:


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:

Code:


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 herauszufinden, welchem Typ die Klasse angehört:

Code:


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 ein Flag abfragen, das erst umständlich definiert und gesetzt werden muss, sondern muss nur überprüfen, ob der Rückgabewert von getInterface() ungleich oder gleich NULL ist:

Code:


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 auch gute Möglichkeiten, dieses zu vereinfachen, z.B. durch eine simple Template Methode:

Code:


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:

Code:


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);




Ich freue mich natürlich immer auf unterstützende Kritik, Meinungen, Kommentare, Liebes sowie Hassbriefe.

mfg Mark (FallenAngel84[]at[]web.de)

PS: Sollte GUID ein unbekannter Typbezeichner sein so nutzt einfach das hier:

Code:

#include <guiddef.h>

Gibt es noch irgendwelche Fragen, oder wollen Sie über den Artikel diskutieren?

Editieren Versionen Linkpartnerschaft Top Printversion

Haben Sie einen Fehler gefunden? Dann klicken Sie doch auf Editieren, und beheben den Fehler, keine Angst, Sie können nichts zerstören, das Tutorial kann wiederhergestellt werden

Sprachenübersicht/Programmierung/C / C++/ C#/OOP/Interfaces Tutorial