Objekt-Typ orientierte Programmierung

Nachdem wir die Softwarearchitektur der Tierklasse mit dem Funktionszeiger so verbessern konnten, dass wir die Quelltexte, die einem Objekttyp zugehören, nicht mehr trennen müssen, haben wir nun das Problem, dass die Instanzen mit jeder objektorientierten Funktion weiter wächst. Tiere jagen() unterschiedlich, einige werfen Junge andere legen Eier. Je mehr Unterschiede über Funktionen abgebildet werden, desto mehr Funktionszeiger müssen über Funktionspointer abgebildet werden.

Vielleicht ist in den vorherigen Kapiteln eines aufgefallen: Wir haben das Problem sehr allgemein gelöst: Für jedes einzelne Objekt gibt es entweder die Typ-Variable, die den Objekttyp angibt und mit der man das Verhalten des Objektes in allen Funktionen bestimmt wird oder es gibt Funktionszeiger, an der man sogar einzelnes Verhalten der Objekte steuern kann. Wenn nun Katzen bei GibLaut() „Miau“ sagen sollen, dann ist es eigentlich nicht erforderlich, dass alle Katzen auf die Funktion zeigen, die „Miau“ ausgibt. Und wir brauchen auch nicht die Möglichkeit, dass Katzen auf Piepen oder Bellen umkonfiguriert werden müssen, was bei einzelnen Funktionszeigern ja der Fall sein kann.

Was unter objektorientierter Programmierung gelehrt wird, sollte also eher objektTYPorientierte Programmierung heißen. Schauen wir uns nun die übliche Implementierung an, wie Objekt(typ)orientierte Programme aufgebaut werden. Wenn die Funktionen grundsätzlich vom Typ abhängen, so müssen Objekte nicht mehr jedes für sich alle Funktionspointer mitführen, es reicht aus, wenn es eine Tabelle gibt, die den Typ beschreibt.

Alle Eigenschaften, die für alle Katzen gelten lassen sich also in einem einzigen Datenobjekt zusammenfassen. Diese Datenstruktur nennt man VirtualTable. Sie enthält den Typ des Objektes und die Funktionszeiger, die für alle Objekte dieses Typs gelten:

#include "stdio.h"
#include "stdlib.h"
 
enum ObjType
{
  OBJTYPE_CAT,
  OBJTYPE_DOG,
  OBJTYPE_BLACKBIRD  
};
 
struct TierVTable
{
  ObjType Type;
 
  void (*GibLaut)();
  int (*JageFutter)( struct Tier * ); 
  char const * (*FutterArt)();
};
 
class Tier
{
public:
  TierVTable & vtable;
 
  char Name[64];
  int  Fellfarbe;
  int  KrallenLaenge;
  int  AnzahlBeine;
 
  Tier( TierVTable & initVTable ) : vtable( initVTable ) {}
};
 
struct TierVTable TierVTableKatze;
struct TierVTable TierVTableHund;
struct TierVTable TierVTableAmsel; 
 
class Hund : public Tier
{
public:
  int  GefangeneKatzen;
 
  Hund() : Tier( TierVTableHund ) {}
};
 
class Katze : public Tier
{
public:
  int   GefangeneVoegel;
  Katze() : Tier( TierVTableKatze ) {}
};
 
class Amsel : public Tier
{
public:
  int  GefangeneInsekten;
  Amsel() : Tier( TierVTableAmsel ) {}
};
 
static void GibLautKatze() { printf( "Miau\n" ); }
static void GibLautHund() { printf( "Wuff\n" ); }
static void GibLautAmsel() { printf( "Piep\n" ); }
 
static char const * FutterArtKatze() { return "Vögel"; }
static char const * FutterArtHund() { return "Katzen"; }
static char const * FutterArtAmsel() { return "Insekten"; }
 
static int JageFutterKatze( class Tier * thisPtr ) { return ++static_cast< Katze * >( thisPtr )->GefangeneVoegel; }
static int JageFutterHund ( class Tier * thisPtr ) { return ++static_cast< Hund  * >( thisPtr )->GefangeneKatzen; }
static int JageFutterAmsel( class Tier * thisPtr ) { return ++static_cast< Amsel * >( thisPtr )->GefangeneInsekten; }
 
void initVTables()
{
  TierVTableKatze.Type = OBJTYPE_CAT;
  TierVTableKatze.GibLaut = GibLautKatze;
  TierVTableKatze.JageFutter = JageFutterKatze;
  TierVTableKatze.FutterArt = FutterArtKatze;
 
  TierVTableHund.Type = OBJTYPE_DOG;
  TierVTableHund.GibLaut = GibLautHund;
  TierVTableHund.JageFutter = JageFutterHund;
  TierVTableHund.FutterArt = FutterArtHund;
 
  TierVTableAmsel.Type = OBJTYPE_BLACKBIRD;
  TierVTableAmsel.GibLaut = GibLautAmsel;
  TierVTableAmsel.JageFutter = JageFutterAmsel;
  TierVTableAmsel.FutterArt = FutterArtAmsel;
}
 
void LautUndZweimalJagen( Tier * tier )
{
  tier->vtable.GibLaut();
  printf( "Tier fängt Nahrung: bisher %d %s gefangen\n", tier->vtable.JageFutter( tier ), tier->vtable.FutterArt() );
} 
 
int main( void )
{
  initVTables();
 
  Tier *tier1, *tier2;
 
  tier1 = new Katze();
  tier2 = new Hund();
 
  LautUndZweimalJagen( tier1 );
  LautUndZweimalJagen( tier2 );
  LautUndZweimalJagen( tier1 );
 
  delete tier1;
  delete tier2;
 
  return EXIT_SUCCESS;  
}

Dies ist nun ein Programm, was abhängig vom Objekttyp unterschiedliche Methoden ruft. Je nach Objekttyp hat das Objekt eine andere Instanz der struct TierVTable, anhand der entschieden wird, welche Funktion nun gewählt wird.

Alles, was hier passiert lässt sich auch in C problemlos beschreiben.

Anhand des Objektes wird die Funktion gewählt und ihr das Objekt übergeben (wie es bei der Funktion JageFutter() geschieht). Bei JageFutter() heißt der Zeiger auf das Objekt thisPtr. Das übergebene Objekt wird in C++ innerhalb der Methode mit dem Schlüsselwort this angesprochen.

Obwohl der Funktion LautUndZweimalJagen() nur ein Tier übergeben wird und die Funktion nicht weiß, um welches Tier es sich handelt, sorgt der objekt(typ)-orientierte Programmieransatz dafür, dass die Funktion für jedes Tier korrekt ausgeführt wird.