Dies ist eine alte Version des Dokuments!


Was ist objektorientierte Programmierung?

Mancher wird sich wundern, wieso objektorientierte Programmierung jetzt erst im Tutorial auftaucht, schließlich sind Klassen bereits bekannt und auch Information-Hiding (public, protected, private) ist bereits bekannt. Auch wenn man es von Lehrern an Schulen oder Professoren an den Hochschulen so beigebracht bekommt, dass dies Teile der objektorientierten Programmierung wären - alles Quatsch, bisher tauchte im Tutorial noch überhaupt keine objektorientierte Programmierung auf.

Das wollen wir nun ändern. Beginnen wir damit, uns zunächst bewusst zu machen, was OOP überhaupt ist. Wenn wir uns an einem Objekt orientieren, dann müssen wir das Objekt fragen, welche Art Objekt es denn gerne sein möchte. Ein Beispiel:

enum ObjType
{
  OBJTYPE_LINE,
  OBJTYPE_CIRCLE,
  OBJTYPE_RECT  
}
 
struct GraphicElement
{
  ObjType Type;
 
  double XPosition, YPosition;
  double Additional1, Additional2;
};

Eine Instanz von GraphicElement, beschreibt mit Type die Art des Objektes, die es darstellt. Nun kann man Funktionen schreiben, die objektorientiert arbeiten:

void DrawElement( struct GraphicElement * element )
{
  switch( element->Type )
  {
    case OBJTYPE_LINE:   DrawLine  ( XPosition, YPosition, Additional1 /* X2 */ , Addition2 /* Y2 */ ); break;
    case OBJTYPE_CIRCLE: DrawCircle( XPosition, YPosition, Additional1 /* Radius */ ); break; 
    case OBJTYPE_RECT:   DrawRect  ( XPosition, YPosition, Additional1 /* Width */, Addition2 /* Height */ ); break;
  }  
}
 
double CalcArea( struct GraphicElement * element )
{
  switch( element->Type )
  {
    case OBJTYPE_LINE:   return 0.0;
    case OBJTYPE_CIRCLE: return 6.28319 * element->Additional1 * element->Aditional; // 2*pi*radius^2 
    case OBJTYPE_RECT:   return element->Additional1*element->Additional2;
  }  
 
  return 0.0; 
}

Das ist Objektorientiert. Und wichtig ist, dass hier nichts verwendet wurde, was C++ erfordert, denn Objektorientierte Programmierung ist auch in C möglich. Und um zu verstehen, was OOP ist, werden wir uns OOP in C anschauen und dann sehen, wie C++ uns dabei unterstützt.

Hier lässt sich der Objekttyp einfach durch das setzen der Variable Type des GraphicElements setzen und verändern. Die beiden Funktionen DrawElement und CalcArea entscheiden nun in Abhängigkeit zum Member Type, wie sie ablaufen sollen. Wir können diese Entscheidung nun aber auch gleich dem Objekt überlassen, indem wir sie um die entsprechenden Funktionspointer erweitern:

struct GraphicElement
{
  ObjType Type;
 
  double XPosition, YPosition;
  double Additional1, Additional2;
 
  void (*Draw)( struct GraphicElement * );
  double (*CalcArea)( struct GraphicElement * );  
};

Ab nun wird der ObjType eigentlich nicht mehr benötigt, da nun zwei Funktionszeiger festlegen, welche Funktionen gerufen werden sollen:

void DrawLine( struct GraphicElement * element ) { DrawCircle( element->XPosition, element->YPosition, element->Radius ); }
void DrawLine( struct GraphicElement * element ) { DrawLine( element->XPosition, element->YPosition, element->Additional1, element->Addition2 ); }
void DrawRect( struct GraphicElement * element ) { DrawRect( element->XPosition, element->YPosition, element->Additional1, element->Addition2 ); }
 
double CalcAreaLine( struct GraphicElement * element ) { return 0.0; }
double CalcAreaLine( struct GraphicElement * element ) { return 6.28319 * Additional1 * Aditional; }
double CalcAreaLine( struct GraphicElement * element ) { return Width*Height; }
 
GraphicElement * NewCircle( double x, double y, double radius )
{
  GraphicElement * result = (GraphicElement *)malloc( sizeof( GraphicElement ) );
 
  if( result )
  {
    result->ObjectType = OBJTYPE_CIRCLE;
 
    result->XPosition = x;
    result->YPosition = y;
    result->Additional1 = r;
    result->Draw = DrawCircle;
    result->CalcArea = CalcAreaCircle; 
  }
 
  return result;   
}
 
int main( void )
{
  GraphicElement *ge;
 
  ge = NewCircle( 5.0, 5.0, 2.5 );
 
  printf( "Fläsche des Kreises: %f", ge->CalcArea( ge ) );
 
  free( ge );
 
  return EXIT_SUCCESS;  
}

Auch dies ist objektorientierte Programmierung. Grundsätzlich kann sich jedes Objekt entscheiden, welche Draw-Routine und welche CalcArea-Routine verwendet wird. Aber in der Regel ist dies so auch gar nicht erforderlich, zumeist orientiert man sich am Datentyp des Objektes. Ein Kreis wird also nicht mit der DrawLine-Funktion gezeichnet und die Fläche nicht der CalcAreaRect-Funktion berechnet. Es wird also immer so sein, dass ein Typ einen Satz von Funktionen verwendet.

Objektorientierte Programmierung sollte also eher Objekttyporientierte Programmierung heißen, wenn man es genau nimmt. 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:

struct GraphicObjectVTable
{
  ObjType Type;
 
  void (*Draw)( struct GraphicElement * );
  double (*CalcArea)( struct GraphicElement * );  
};
 
struct GraphicElement
{
  GraphicObjectVTable * vtable;
 
  double XPosition, YPosition;
  double Additional1, Additional2; 
};
 
struct GraphicObjectVTable GraphicObjectVTableLine;
struct GraphicObjectVTable GraphicObjectVTableCircle;
struct GraphicObjectVTable GraphicObjectVTableRect; 
 
void initVTables()
{
  GraphicObjectVTableLine.Type = OBJTYPE_LINE;
  GraphicObjectVTableLine.Draw = DrawLine;
  GraphicObjectVTableLine.CalcArea = CalcAreaLine;
 
  GraphicObjectVTableCircle.Type = OBJTYPE_CIRCLE;
  GraphicObjectVTableCircle.Draw = DrawCircle;
  GraphicObjectVTableCircle.CalcArea = CalcAreaCircle;
 
  GraphicObjectVTableRect.Type = OBJTYPE_RECT;
  GraphicObjectVTableRect.Draw = DrawRECT;
  GraphicObjectVTableRect.CalcArea = CalcAreaRect;
}
 
GraphicElement * NewCircle( double x, double y, double radius )
{
  GraphicElement * result = (GraphicElement *)malloc( sizeof( GraphicElement ) );
 
  if( result )
  {
    result->vtable = &GraphicObjectVTableCircle;
 
    result->XPosition = x;
    result->YPosition = y;
    result->Additional1 = r;
  }
 
  return result;   
}
 
int main( void )
{
  initVTables();
 
  GraphicElement *ge;
 
  ge = NewCircle( 5.0, 5.0, 2.5 );
 
  printf( "Fläsche des Kreises: %f", ge->vtable->CalcArea( ge ) );
 
  free( ge );
 
  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 GraphicObjectVTable, anhand der entschieden wird, welche Funktion nun gewählt wird. Anhand des Objektes wird die Funktion gewählt und ihr das Objekt übergeben. Das übergebene Objekt wird in C++ innerhalb der Methode mit dem Schlüsselwort this angesprochen.