SELFHTML: Perl / Perl-Sprachelemente / Objektorientiertes Programmieren
Author: Redaktion SELFHTML, selfhtml81@selfhtml.org
Description: Was objektorientierte Programmierung auszeichnet, und wie sie in Perl funktioniert.
Keywords: SELFHTML, Perl, OOP, objektorientierte Programmierung, Objekte, Klassen, Instanzen, Eigenschaften, Methoden, Objekteigenschaften, Objektdaten, Konstruktorfunktion, Destruktorfunktion
Date: 2005-11-09T00:29:27+01:00

| # Objektorientiertes Programmieren
| Objektorientierte Programmierung (OOP) Objektorientierte Programmierung in Perl Einfaches Beispiel: das HTMLprint-Objekt Beispiel mit Objektdaten: das VCard-Objekt

## Objektorientierte Programmierung (OOP)

Vor allem bei größeren Programmiervorhaben ist es wichtig, nicht einfach draufloszuprogrammieren, sondern sich vorher darüber Gedanken zu machen, was die Anwendung können, in welcher Form sie Daten speichern soll, welche Benutzerschnittstellen erforderlich sind usw. In dieser Phase ist es gar nicht wichtig und eher hinderlich, dauernd irgendwelche Quellcode-Fetzen im Kopf zu haben. Besser ist es, vom natürlichen Denken ausgehend die Anwendung zu modellieren.

Das natürliche Denken des modernen Menschen ist aber nicht so "prozedural" ausgerichtet wie die klassische Programmierung. Es vollzieht sich nicht in Sprungadressen und Kontrollstrukturen, sondern wird an größere eigenständige Einheiten (Menschen, Maschinen usw.), die in der Lage sind, Informationen und Fähigkeiten beizusteuern, miteinander zu kommunizieren und dadurch etwas Neues zu schaffen, gebunden.

Auch ein CGI-basiertes Gästebuch lässt sich so denken. Da gibt es beispielsweise einen Formular-Manager, der sich um die Auswertung ausgefüllter HTML-Formulare kümmert, einen Daten-Manager, dessen Aufgabe das zuverlässige und einheitliche Speichern und Einlesen von Daten ist, und einen Konfigurations-Manager, der entscheidet, welche Möglichkeiten des Gästebuchs im konkreten Fall genutzt werden und in welcher Form. Dabei muss der Konfigurations-Manager beispielsweise den Daten-Manager um das Einlesen bestimmter Konfigurationsdaten bitten. Selber darf er das nicht, das ist nun mal Aufgabe des Daten-Managers. Genausowenig darf der Daten-Manager sich einfach die HTML-Formulare holen, die vom Anwender abgesendet wurden. Dafür ist der Formular-Manager zuständig, der dann vielleicht wieder beim Konfigurations-Manager nachfragen muss, wie die Daten weiterzugeben sind, und der Antwort entsprechend die Daten an den Daten-Manager zum Abspeichern weitergibt.

Solche teamfähigen Einheiten werden in der objektorientierten Programmierung als Objekte bezeichnet. Ein Objekt hat Eigenschaften und kann etwas tun. Einige der Eigenschaften und Fähigkeiten stammen von anderen Objekten, die das Objekt selbst eingebunden und damit geerbt hat, und andere wiederum sind ganz eigene und besondere Eigenschaften und Fähigkeiten dieses Objekts.

Diese Dokumentation ist allerdings nicht der Rahmen, um die Theorie der objektorientierten Programmierung auszubreiten. Ein paar wichtige Begriffe sollten Sie trotzdem kennen:

Klasse:
Eine Klasse ist die Summe der programmierten Fähigkeiten eines Objekts. Alles, woraus ein Objekt besteht und was es kann, muss irgendwo programmiert sein. Eine Klasse ist aber "abstrakt" programmiert, sie führt noch keinen konkreten Code aus. So wie man, wenn man an "Handy" denkt, einen bestimmten Begriff von so einem Gerät hat, ist ein konkretes Objekt in der objektorientierten Programmierung nur von seiner Klasse ableitbar. Ein konkretes Objekt hat die Eigenschaften und Fähigkeiten seiner Klasse, so wie man von einem Handy, den man in der Hand hat, sagt, dass es ein Handy ist, weil er die Eigenschaften und Funktionen eines Handys hat, also dem Begriff des Handys entspricht. In einer objektorientierten Anwendung können Sie sagen: für die Aufgabe, die ich lösen möchte, brauche ich erst mal eine bestimmte Klasse, so wie man sagen könnte: für diese Aufgabe brauche ich einen Handy.

Instanz:
Eine Instanz ist ein konkretes Objekt einer bestimmten Klasse. Mit dem Begriff vom Handy allein können Sie noch keine Aufgaben lösen. Sie brauchen ein ganz reales Handy dazu. Genauso brauchen Sie eine ganz reale Instanz einer Klasse, um eine Aufgabe innerhalb Ihrer Anwendung objektorientiert anzupacken. Ihr konkretes Objekt erhält einen individuellen Namen, unter dem Sie es ansprechen können.

Eigenschaften und Methoden:
Von einer Objektinstanz können Sie nicht mehr verlangen als das, wozu das Objekt aufgrund seiner Klasse in der Lage ist. Das, wozu ein Objekt in der Lage ist, ist dadurch festgelegt, was in seiner Klasse programmiert ist. Eigenschaften (auch Attribute genannt) sind Variablen, die Ihnen zur Verfügung stehen, wenn Sie eine Instanz eines Objekts erzeugt haben. Methoden sind Funktionen, die Ihnen eine Objektinstanz zur Verfügung stellt. Eine Instanz der Klasse "Formular-Manager" könnte beispielsweise als Eigenschaft eine Datenstruktur anbieten, in der sie eingelesene Formulardaten bequem zur Verfügung stellt. Als Methoden könnte diese Instanz zum Beispiel Filterfunktionen enthalten, um etwa nur die Daten aus versteckten Formularfeldern anzubieten. In Ihrer Anwendung können Sie dann Eigenschaften und Methoden verwenden, indem Sie auf deren Namen innerhalb einer konkreten Instanz "zeigen". Das ist so, als ob Sie an dem Laptop, den Sie vor sich haben, die Funktion "suche den nächsten ausreichend starken WLAN-Hotspot" anstoßen (eine Methode) und bei gefundenem Hotspot am Display dessen Standort ablesen können (eine Eigenschaft).

Konstruktor- und Destruktor-Funktion:
Eine Konstruktorfunktion übernimmt die Handlung, eine Instanz eines Objektes zu erzeugen. Sie macht gewissermaßen aus dem Begriff von einem Handy ein reales Handy. Nun sind Sie damit unterwegs, und irgendwann brauchen Sie das Handy nicht mehr. Dann ist es nur unnötiges Gepäck. Deshalb können Sie es dann abschalten. Zumindest im Computer ist das sinnvoll, denn dadurch werden wieder Speicher-Ressourcen frei. Zum Abschalten (Zerstören) von Objektinstanzen gibt es daher eine Destruktor-Funktion.

## Objektorientierte Programmierung in Perl

Der Perl-Interpreter unterstützt objektorientiertes Programmieren seit der Version 5.0. Um eine Klasse zu programmieren, also das, wovon sich eine Objektinstanz ableiten lässt, brauchen Sie in Perl ein eigenes Package, also einen eigenen Namensraum. Subroutinen, die innerhalb des Packages notiert sind, sind die Methoden von Objekten dieser Klasse. Auch Objekteigenschaften können Sie innerhalb solcher Methoden auf bestimmte Weise definieren. Packages lassen sich zwar auch innerhalb eines Perl-Scripts definieren (siehe Beispiel zur Perl-Funktion package), aber im Normalfall ist ein Package eine separate Perl-Datei, die keine andere Aufgabe hat als die, in Perl-Scripts "eingebunden" zu werden. Kurzum: ein Package ist in Perl meist ein Modul mit der entsprechenden Dateiendung `.pm`. Von daher haben in Perl modul-orientiertes Programmieren und objektorientiertes Programmieren viel miteinander zu tun.

Ein Package ist allerdings noch nicht automatisch eine Klasse für Objekte. Packages sind lediglich Namensräume und damit das "Klima, in dem Objektklassen gedeihen können". Damit Perl eine Klasse für Objekte als solche erkennt, muss sie als solche definiert und erstellt werden.

Ebenso ist es mit den Objektinstanzen. Zunächst müssen Sie Perl mitteilen, dass Sie eine Instanz eines Objekts erzeugen möchten. Ist das gelungen, können Sie anschließend Eigenschaften und Methoden dieses Objekts über eine bestimmte Syntax ansprechen.

### Beachten Sie:

Perl bietet auch weitere Features der objektorientierten Programmierung an, etwa Vererbung und Methodenbindung an Variablen. In diesem einführenden Abschnitt werden solche weiteren Features jedoch nicht behandelt.

## Einfaches Beispiel: das HTMLprint-Objekt

In diesem Beispiel wird gezeigt, wie Sie ein Modul als Objektklasse erstellen, in einem Perl-Script eine Instanz dieses Objekts erzeugen und mit Methoden des Objekts arbeiten. Das Beispiel realisiert ein einfaches Objekt für HTML-Ausgaben.

### Beispiel der Moduldatei HTMLprint.pm:

    package HTMLprint;

    sub new {
     my $Objekt = shift;
     my $Referenz = {};
     bless($Referenz,$Objekt);
     return($Referenz);
    }

    sub Anfang {
      my $Objekt = shift;
      my $Titel = shift;
      print "Content-type: text/html\n\n";
      print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">', "\n";
      print "<html><head><title>$Titel</title></head><body>\n";
    }

    sub Titel {
      my $Objekt = shift;
      my $Ausgabetext = shift;
      print "<h1>$Ausgabetext</h1>\n";
    }

    sub Text {
      my $Objekt = shift;
      my $Ausgabetext = shift;
      print "<p>$Ausgabetext</p>\n";
    }

    sub Ende {
      my $Objekt = shift;
      print "</body></html>\n";
    }

    1;

### Beispiel eines vollständigen CGI-Scripts in Perl:

Anzeigebeispiel: So sieht's aus (Zum Aufruf des Scripts ist eine Internet-Verbindung erforderlich)

    #!/usr/bin/perl -w

    use strict;
    use CGI::Carp qw(fatalsToBrowser);
    use HTMLprint;

    my $html = HTMLprint -> new();
    $html->Anfang("Ganz elegantes Perl");
    $html->Titel("Ganz elegantes Perl");
    $html->Text("Popeliges HTML, aber sehr modern programmiert!");
    $html->Ende();

### Erläuterung:

Die Moduldatei beginnt einfach mit einer package-Anweisung, die den Namen des Moduls nennt. Der Name sollte identisch mit dem Vornamen der Moduldatei sein. Wenn die Moduldatei also wie im Beispiel HTMLprint.pm heißt, sollte die Anweisung `package HTMLprint;` lauten.

Die Moduldatei im Beispiel enthält insgesamt fünf Subroutinen. Die erste davon ist die Konstruktorfunktion. Sie erhält üblicherweise den Namen `new`, könnte aber theoretisch auch einen anderen Namen erhalten. Wie eine Konstruktorfunktion aufgebaut ist, kann von Fall zu Fall variieren. In jedem Fall muss die Konstruktorfunktion jedoch einen Aufruf der Funktion `bless` enthalten. Diese bindet eine Referenz (einen Zeiger) an eine Klasse, sodass anschließend über diese Referenz auf das Objekt zugegriffen werden kann. Die im obigen Beispiel gezeigte Konstruktorfunktion leistet zwar schon ein wenig mehr als für das Beispiel benötigt wird, doch sie eignet sich in dieser Form einfach gut als Standard-Konstruktorfunktion, bei der man wenig falsch machen kann.

Mit `my $Objekt = shift;` wird einfach der erste Parameter eingelesen, den die Konstruktorfunktion `new` übergeben bekommt. Sie bekommt nämlich, wenn sie aufgerufen wird, automatisch als ersten Parameter den Klassennamen übergeben, im Beispiel also den Namen `HTMLprint`. Das ist im Beispiel der Wert, der anschließend in `$Objekt` steht.
Mit `my $Referenz = {};` wird eine Referenz, also ein Zeiger deklariert. Dieser Zeiger wird benötigt, damit die Funktion `bless` korrekt bedient werden und den Zeiger an das Objekt binden kann. Nun ist die Frage, wie aus der skalaren Variable `$Referenz` eine Referenz werden soll. Sie muss sich ja auf irgendetwas beziehen, worauf sie zeigt. Ideal ist für diesen Zweck die Zuweisung `{}`. Dies ist einfach eine Referenz auf einen leeren unbenannten Hash. Ideal ist das deshalb, weil dieser Hash auch für die Definition von Objektdaten geeignet ist - was allerdings in diesem ersten einfachen Beispiel noch nicht geschieht. So bleibt es bei dem leeren Hash, und `$Referenz` ist ein Zeiger.
Mit `bless($Referenz,$Objekt);` wird die nötige Verbindung zwischen Referenz und Objekt hergestellt. Anschließend wird `$Referenz` zurückgegeben.

Die vier übrigen Subroutinen in der Moduldatei HTMLprint.pm sind sich alle recht ähnlich. Sie erzeugen mit Hilfe von `print`-Anweisungen HTML-Code. Die Subroutine `Anfang` schreibt zudem zunächst den nötigen HTTP-Header für HTML-Ausgaben.

Für alle Subroutinen, die als Methoden eines Objekts fungieren, gilt: sie bekommen automatisch mindestens einen Parameter übergeben, und zwar den Zeiger auf das Objekt, der in der Konstruktor-Funktion erzeugt und an das Objekt gebunden wurde. Die Subroutinen im Beispiel benötigen diesen Zeiger nicht. Aber `Anfang`, `Titel` und `Text` erwarten noch einen zweiten ganz gewöhnlichen Parameter, nämlich den Inhalt, den sie in HTML ausgeben sollen. Nun könnten sich die Routinen den Inhalt dieses zweiten Parameters auch direkt mit `$_[1]` holen. Aber die Notation mit dem Aufruf der `shift`-Funktion hat den Vorteil, dass die Parameterliste dabei sauber abgearbeitet und der Reihe nach gelöscht wird. Mit dem jeweils zweiten Aufruf von `shift` kommen die drei erwähnten Subroutinen also an den zweiten Parameter.

Wichtig zu erwähnen ist noch, dass pm-Moduldateien am Ende stets eine einsame Anweisung `1;` (oder etwas ähnliches) enthalten müssen. Das bedeutet, die Moduldatei gibt einen wahren Wert zurück. Andernfalls würde der Perl-Interpreter einen Fehler melden.

Das eigentliche Perl-Script ist im Beispiel recht einfach. Mit der Funktion use und der Anweisung `use HTMLprint;` bindet es die Moduldatei HTMLprint.pm ein. In dieser Form eingebunden, muss sich die HTMLprint.pm entweder im gleichen Verzeichnis befinden wie das Script, oder in einem der zentralen Verzeichnisse, in denen Perl nach Modulen sucht (mehr darüber im Abschnitt Speicherorte für Module und die Liste @INC).

Mit `my $html = HTMLprint -> new();` wird die Konstruktorfunktion der Moduldatei aufgerufen. Dies ist die entscheidende Anweisung, mit der eine neue Istanz des HTMLprint-Objekts erzeugt wird! Die Anweisung besteht darin, dass ein Skalar, der im Beispiel den Namen `$html` erhält, deklariert und mit einem Wert initialisiert wird. Der zugewiesene Wert besteht darin, dass der Name der gewünschten Objektklasse, also `HTMLprint`, mit Hilfe des Zeigeoperators `->` auf die Konstruktorfunktion `new` zeigt. Da die Konstruktorfunktion eine Referenz zurückgibt, die durch den `bless`-Aufruf an das Objekt gebunden ist, wird `$html` automatisch zu einer Referenz, also zu einem Zeiger auf die neu erzeugte Objektinstanz. Über `$html` und den Zeigeoperator `->` kann das Script fortan auf die Subroutinen des eingebundenen Moduls zugreifen. Im Objektzusammenhang spricht man allerdings nicht mehr von Subroutinen, sondern von Methoden.
Mit einer Anweisung wie `$html->Anfang("Ganz elegantes Perl");` greift das Script also auf eine Methode des `HTMLprint`-Objekts zu, und zwar auf die Objektinstanz, die an den Zeiger `$html` gebunden ist.

## Beispiel mit Objektdaten: das VCard-Objekt

Das folgende Beispiel zeigt, wie sich Daten mit Objekten verwalten lassen. Es handelt sich dabei um Adressdaten im international standardisierten VCard-Format (typische Dateiendung: *.vcf), wie es vom Internet Mail Consortium (IMC) entwickelt und in der RFC 2426 beschrieben wird. In der Moduldatei VCard.pm wird die VCard-Klasse mit den Objektdaten und Methoden definiert. Im Hauptscript werden dann zwei Objektinstanzen dieser Klasse angelegt, und die Methoden des Objekts werden ausprobiert.

### Beispiel der Moduldatei VCard.pm:

    package VCard;

    $Objekte = 0;

    sub new {
     my $Objekt = shift;
     my $Version = shift;
     my $Referenz = {};
     bless($Referenz,$Objekt);
     $Objekte += 1;
     $Referenz->VCinit($Version);
     return($Referenz);
    }

    sub VCinit {
      my $Objekt = shift;
      my $Version = shift;
      $Objekt->{BEGIN} = "VCARD";
      $Objekt->{VERSION} = $Version;
      $Objekt->{END} = "VCARD";
    }

    sub VCset {
      my $Objekt = shift;
      my $Schluessel = shift;
      my $Wert = shift;
      $Objekt->{$Schluessel} = $Wert;
    }

    sub VCget {
      my $Objekt = shift;
      my $Schluessel = shift;
      return($Objekt->{$Schluessel});
    }

    sub VCsave {
      my $Objekt = shift;
      my $Datei = shift;
      open(DATEI, ">$Datei") or return(-1);
      print DATEI "BEGIN:VCARD\nVERSION:$Objekt->{VERSION}\n";
      while ((my $Schluessel, my $Wert) = each %{ $Objekt } ) {
        next if ($Schluessel eq 'BEGIN' or $Schluessel eq 'VERSION' or $Schluessel eq 'END');
        $Schluessel =~ s/_/\;/g;
        print DATEI "$Schluessel:$Wert\n";
      }
      print DATEI "END:VCARD\n";
      close(DATEI);
      return($Datei);
    }

    sub VCopen {
      my $Objekt = shift;
      my $Datei = shift;
      $Objekt->VCreset();
      open(DATEI, "<$Datei") or return;
      my @Zeilen = <DATEI>;
      close(DATEI);
      foreach (@Zeilen) {
       (my $Schluessel, my $Wert) = split(/:/);
        $Schluessel =~ s/\;/_/g;
        $Objekt->{$Schluessel} = $Wert;
      }
      return( %{ $Objekt } );
    }

    sub VCreset {
      my $Objekt = shift;
      %{ $Objekt } = ();
    }

    1;

### Beispiel eines vollständigen CGI-Scripts in Perl:

    #!/usr/bin/perl -w

    use strict;
    use CGI::Carp qw(fatalsToBrowser);
    use VCard;

    print "Content-type: text/html\n\n";
    print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">', "\n";
    print "<html><head><title>Testausgabe</title></head><body>\n";
    print "<h1>Tests mit dem VCard-Objekt</h1>\n";

    my $Thilo = VCard->new("3.0");
    my $Ina = VCard->new("3.0");

    print "<p><b>Wert der im Modul erzeugte Objekteigenschaft BEGIN:</b> $Ina->{BEGIN}<br>\n";
    my $Sicherung = $Ina->{BEGIN};
    $Ina->{BEGIN} = "fangen wir an";
    print "<b>BEGIN nach der Wert&auml;nderung:</b> $Ina->{BEGIN}<br>\n";
    $Ina->{BEGIN} = $Sicherung;
    print "Da dies aber Unsinn ist, wurde der Wert wieder auf '$Ina->{BEGIN}' gesetzt.</p>\n";

    $Thilo->VCset("FN","Thilo Teufel");
    $Thilo->VCset("TEL_CELL_VOICE","(0170) 398373740");
    $Ina->VCset("FN","Ina Bikina");
    $Ina->VCset("EMAIL_PREF_INTERNET","bikina\@example.org");

    print "<p><b>Name:</b> ",$Thilo->VCget('FN'),"<br>\n";
    print "<b>Handy:</b> ",$Thilo->VCget('TEL_CELL_VOICE'),"</p>\n";
    print "<p><b>Name:</b> ",$Ina->VCget('FN'),"<br>\n";
    print "<b>Handy:</b> ",$Ina->VCget('EMAIL_PREF_INTERNET'),"</p>\n";
    print "<p><b>Anzahl angelegter Objekte:</b> $VCard::Objekte </p>\n";

    if(my $Datei = $Thilo->VCsave("/daten/vcard/teufel.vcf")) {
     print "<p>VCard von Thilo Teufel in <tt>$Datei</tt> gespeichert!<br>"
    }
    else {
     print "VCard von Thilo Teufel konnte nicht gespeichert werden!";
    }
    $Thilo->VCreset();
    print "<p><b>Name von Thilo nach Objekt-Reset:</b> ",$Thilo->VCget('FN'),"<br>\n";
    print "<b>Handy von Thilo nach Objekt-Reset:</b> ",$Thilo->VCget('TEL_CELL_VOICE'),"</p>\n";

    if($Thilo->VCopen("/daten/vcard/teufel.vcf")) {
     print "VCard von Thilo Teufel wieder ge&ouml;ffnet!<br>";
     print "<p><b>Name von Thilo nach Datei&ouml;ffnen:</b> ",$Thilo->VCget('FN'),"<br>\n";
     print "<b>Handy von Thilo nach Datei&ouml;ffnen:</b> ",$Thilo->VCget('TEL_CELL_VOICE'),"</p>\n";
    }
    else {
     print "VCard von Thilo Teufel konnte nicht ge&ouml;ffnet werden!";
    }

    print "</body></html>\n";

### Beispiel der erzeugten Datei teufel.vcf:

    BEGIN:VCARD
    VERSION:3.0
    TEL;CELL;VOICE:(0170) 398373740
    FN:Thilo Teufel
    END:VCARD

### Erläuterung:

Im Prinzip funktioniert dieses Beispiel genauso wie das Beispiel mit dem HTMLprint-Objekt - deshalb interessieren an dieser Stelle nur diejenigen Dinge, die anders sind als im ersten Beispiel.

Objektdaten (oder die Eigenschaften eines Objekts) sind Variablen. Es genügt jedoch nicht, solche Variablen einfach irgendwo in der Moduldatei zu deklarieren. Eine Variable wie der Skalar `$Objekte`, der im Beispiel zu Beginn der Moduldatei VCard.pm deklariert wird und als Zähler für Objektinstanzen dient, gehört nicht zu den Objektdaten! Solche Variablen sind so genannte Klassenvariablen, da sie nur einmal für die gesamte Klasse und für jede Objektinstanz zur Verfügung stehen. Da das Modul genau einmal im Hauptscript eingebunden wird, stehen diese Variablen auch nur in einer einzigen Ausführung zur Verfügung. Das Hauptscript kann mit der Syntax `$VCard::Objekte` sehr wohl auf den Wert des Skalars `$Objekte` zugreifen. Aber trotzdem gehört diese Variable nicht zum Objekt. Objektdaten sind vielmehr solche, die bei jeder neuen Instanz eines Objekts immer wieder neu zur Verfügung stehen.

Die einfachste Lösung für Objektdaten ist ein Hash. In der Konstruktorfunktion `new` wird ein solcher Hash definiert - mit der Anweisung `my $Referenz = {};`. Viel zu sehen ist dabei nicht von einem Hash. Doch die leeren geschweiften Klammern symbolisieren einen leeren so genannten anonymen Hash. Dieser Hash ist damit eine Art leerer Behälter, der später Objektdaten aufnehmen kann. Der Grund dafür, dass man in der Regel einen Hash für Objektdaten wählt, ist der, dass ein Hash eine dynamische Anzahl von Daten speichern kann. Ein solcher Hash erlaubt es, Objektdaten sowohl im Modul selbst vorzudefinieren, wie auch Objektdaten im Hauptscript zu definieren (im obigen Beispiel passiert beides). Ein Array könnte das zwar theoretisch auch, aber dann wäre im Hauptscript die Suche nach einem gewünschten Objektdatum schwieriger. Hashes erleichtern den Direktzugriff auf Daten durch die benannten Schlüssel. Durch die Zuweisung des anonymen Hashes an einen Skalar - im Beispiel an `$Referenz` - wird dieser Skalar eine Referenz, also ein Zeiger auf den anonymen Hash. Über diesen Zeiger ist der Zugriff auf den anonymen Hash möglich.

Objektdaten werden erzeugt, indem der zunächst anonyme Hash der Konstruktorfunktion mit Daten angefüllt wird. Im Beispiel geschieht das erstmals in der Subroutine bzw. Methode `VCinit`. Diese Methode wird bereits in der Konstruktorfunktion `new` aufgerufen, nämlich mit `$Referenz->VCinit($Version);`. Da `$Referenz` innerhalb der Konstruktorfunktion der Objektzeiger ist, können Sie dort über das Schema `$Referenz->Methode()` andere Subroutinen aufrufen, die dadurch an das Objekt gebunden werden. Durch diese Form des Aufrufs bekommen die Subroutinen auch stets automatisch als ersten Parameter einen Zeiger auf das Objekt übergeben, mit dem sie dann arbeiten können. So auch die Methode `VCinit` im Beispiel. Über den Objektzeiger, den sie sich mit `my $Objekt = shift;` aus der übergebenen Parameterliste holt, kann sie auf das Objekt und damit auch auf den anonymen Hash zugreifen.

Mit einer Anweisung wie `$Objekt->{BEGIN} = "VCARD";` wird eine vorgegebene Objekteigenschaft erzeugt. `$Objekt->{BEGIN}` zeigt auf den anonymen Hash und sagt so viel wie: "hiermit erhält der anonyme Hash einen Schlüssel mit Namen `BEGIN`". Außerdem wird diesem Schlüssel bereits ein Wert zugewiesen, nämlich `VCARD`. Auf die gleiche Weise werden in `VCinit` insgesamt drei Objekteigenschaften erzeugt. Die beiden anderen heißen `VERSION` und `END`. Der Grund für diese Namen ist im Beispiel, dass diejenigen Namen von Eigenschaften als Schlüsselnamen verwendet werden, die eine Datei vom Typ .vcf enthalten können. Wie so eine Datei aussieht, sehen Sie in der dargestellten vom Beispiel erzeugten Datei teufel.vcf. Alle Zeilen einer VCard-Datei bestehen aus einer Eigenschaft und ihrem Wert, getrennt durch einen Doppelpunkt. Drei Zeilen sollten in jeder vcf-Datei vorkommen: `BEGIN:VCARD`, `VERSION:[Versionsangabe]` und `END:VCARD`. Für diese drei Standardzeilen werden im Beispiel die drei gleichnamigen Objekteigenschaften erzeugt.

Weitere Objekteigenschaften werden im Beispiel im Hauptscript erzeugt. Zunächst muss dazu wieder eine Instanz der VCard-Klasse erzeugt werden. Im Beispiel werden zwei Instanzen erzeugt:
`my $Thilo = VCard->new("3.0");
my $Ina = VCard->new("3.0");`
Beim Aufruf der Konstruktorfunktion wird die Versionsnummer für das gewünschte VCard-Format übergeben. Derzeit ist da die Version 3.0 zu notieren.

Nachdem die beiden Objektinstanzen angelegt sind, ist der Zugriff auf sämtliche Objektdaten möglich. Im Hauptscript wird dies zunächst mit dem Objekt für Ina demonstriert. Über den Objektzeiger `$Ina` ist es möglich, mit `$Ina->{BEGIN}` auf den Wert der Objekteigenschaft `BEGIN` zuzugreifen. Das Script zeigt auch, wie sich der Wert einer solchen vorgegebenen Objekteigenschaft ändern lässt. Das Konstrukt `$Ina->{BEGIN}` kann wie ein ganz normaler Skalar behandelt werden.
Da bei der objektorientierten Programmierung das Objekt eine abstrakte Einheit ist, dessen Interna Sie als Benutzer des Objekts eigentlich nichts angehen, sollten Sie darauf verzichten, direkt auf Objekteigenschaften zuzugreifen. Erstens ist nämlich nicht gesagt, dass bei einem (beliebigen) Objekt die Daten überhaupt in einem Hash gehalten werden, und zweitens kann sich die interne Datenhaltung über die verschiedenen Versionen des Moduls hinweg ändern.

Das Hauptscript erzeugt aber auch noch weitere neue Objekteigenschaften und versorgt diese mit Werten. Denn eine VCard-Datei soll ja diverse Adressdaten enthalten. Dies ist im Beispiel durch Aufruf der Methode `VCset` möglich, die in der Moduldatei definiert ist. Mit einer Anweisung wie:
`$Thilo->VCset("FN","Thilo Teufel");`
wird für das Objekt von Thilo eine neue Objekteigenschaft namens `FN` angelegt, die den Wert `Thilo Teufel` zugewiesen bekommt. Im Beispiel sehen Sie, dass sowohl das Objekt von Thilo als auch das Objekt von Ina die Objekteigenschaft `FN` erhalten, jeweils mit eigenen Werten. Daran ist sehr schön erkennbar, dass es sich tatsächlich um verschiedene Instanzen des Objekts `VCard` handelt, wobei jede Instanz ihre eigene Datenwelt hat, ohne dass es Kollisionen mit anderen gleichzeitig existierenden Objektinstanzen gibt.

Während der Aufruf der Methode `VCset` so weit gut nachvollziehbar ist, ist das, was in der Moduldatei innerhalb dieser Methode geschieht, nicht so leicht zu verstehen. Auch `VCset` bekommt - wie alle Methoden - als ersten Parameter automatisch den Objektzeiger übergeben. `VCset` tut nun nichts anderes, als mit `$Objekt->{$Schluessel} = $Wert` dem anonymen Hash des Objekts einen Schlüsselnamen und einen zugehörigen Wert hinzuzufügen. Beides, also den Schlüsselnamen und den Wert, erwartet die Methode als Parameter - daher auch die voranstehenden Anweisungen wie `$Schluessel = shift;`. Die Aufrufe dieser Methode im Hauptscript übergeben ja auch die geforderten beiden Parameter.

Die übrigen Methoden der Moduldatei sind ganz ähnlich aufgebaut. Sie übernehmen verschiedene Aufgaben, was man mit VCard-Daten so alles anstellen kann:
`VCget` liefert zu einem Schlüssel den zugehörigen Wert. Gut für Abfragen. Genaugenommen ist eine Abfrage im Hauptscript wie `$Thilo->VCget('FN')` das gleiche, als ob dort stehen würde `$Thilo->{FN}`. Entscheidungen, ob man solche Methoden braucht, gehören einfach zum Design eines Objekts. Um die Programmpflege zu erleichtern, sollten Sie jedoch immer Methoden dafür bereitstellen.
`VCsave` speichert die Daten eines Objekts in eine vcf-Datei nach dem VCF-Standard. Im Script wird diese Funktion einmal aufgerufen und schreibt dabei die Datei teufel.vcf, deren Inhalt ja dargestellt ist.
`VCopen` liest eine beliebige VCF-Datei und speichert deren Inhalt im zugeordneten Objekt. Diese Methode können Sie also etwa verwenden, wenn Sie existierende VCF-Dateien haben, z.B. durch Export aus den Adressdaten eines VCard-fähigen E-Mail-Programms. `VCreset` löscht alle Daten eines Objekts. Es stellt also den Ursprungszustand des anonymen Hashes wieder her. Benötigt wird diese Methode innerhalb der Moduldatei von `VCopen`. Aber Sie können diese Methode natürlich auch aus dem Hauptscript aufrufen, um eine Objektinstanz wieder in ihren jungfräulichen Zustand zu versetzen. Im Hauptscript des obigen Beispiels wird dies am Objekt von Thilo demonstriert.

Wenn Sie das VCard-Objekts des Beispiels in der Praxis einsetzen wollen, müssen Sie die VCard-Eigenschaftsnamen kennen. Diese sind beim Internet Mail Consortium dokumentiert. Wichtig ist, dass Sie ein Semikolon immer durch Unterstrich ersetzen. So wird eine VCard-Eigenschaft wie `TEL;CELL;VOICE` bei einem Methodenaufruf wie VCset als `TEL_CELL_VOICE` notiert. Einige der möglichen VCard-Eigenschaften enthalten auch noch andere Zeichen wie `=` und `-`. Um diese korrekt zu verarbeiten, müssen Sie die Methoden `VCopen` und `VCsave` des Objekts vermutlich noch erweitern, worauf im Beispiel der Einfachheit halber verzichtet wurde.

| CGI-typische Aufgaben in Perl
| Tokens

© 2007 Impressum