Realisierung eines Objekts in Perl
Klasse, Package und Modul
Für Realisierung einer Klasse braucht man einen Namensraum (das ist der Name der Klasse z. B. CGI). Zudem ist es sinnvoll eine Klasse für sich in eine Datei (wieder mit dem gleichen Namen und der Endung pm) zu schreiben. Damit haben wir schon die äußere Umgebung für eine Klasse unter Perl geschaffen: Ein Modul (z. B. Klassenname.pm) mit einem Namensraum (z. B. Klassenname). Der Name des Moduls und des Packagee sollten übereinstimmen.
Ein noch nicht komplettes Beispiel für eine CGI-Klasse: smallCGI:
package smallCGI;
1;
Konstruktor
Für den Konstruktor kann man im Gegensatz zu anderen Programmiersprachen einen beliebigen Namen auswählen. Es hat sich jedoch der Name new für die Funktion des Konstruktors eingebürgert.
Eine Instanz einer Klasse wird wie folgt erzeugt:
use smallCGI;
my $perli=smallCGI->new();
- smallCGI->new() ruft den Konstruktor new des "Moduls" smallCGI.pm auf. Dabei bewirkt smallCGI->, dass new() mit dem Parameter smallCGI aufgerufen wird (&smallCGI::new(smallCGI)).
Die zugehörige Klasse sieht so aus:
package smallCGI;
sub new {
my $self=shift;
my $ref={};
bless($ref, $self);
$ref;
}
1;
- Mit $self=shift wird der übergebene Name der Klasse smallCGI gelesen.
- Mit $ref={} wird eine Referenz auf einen leeren Hash (kann auch eine Referenz auf etwas anderes sein - meistens wird aber ein Hash bevorzugt benutzt) erzeugt.
- Die Funktion bless ist eine Funktion, die eine Referenz an eine Klasse bindet. Hier wird $ref an die Klasse smallCGI gebunden.
- Die Referenz wird dann zurückgegeben.
new liefert eine Referenz zurück, die an eine Klasse (Modul) gebunden ist. Damit weiß das Objekt $perli, zu welcher Klasse es gehört. Die hier angegebenen Schritte in der Funktion new sind notwendig. new kann aber auch ergänzt werden.
use smallCGI;
my $perli2={};
print $perli2."\n";
my $perli=smallCGI->new();
print $perli."\n";
HASH(0x1a6f0f0)
smallCGI=HASH(0x1a6f1a4)
- Der Inhalt der Hashvariablen enthält den Typ (HASH) und die Speicheradresse (0x1a6f0f0) für die Hashdaten.
- Eine Instanz ist das gleiche wie ein Hash mit dem Zusatz, zu welcher Klasse diese Instanz gehört (smallCGI).
Destruktor
Perl zerstört die Instanzen einer Klasse von selbst. Um jedoch besondere Aktivitäten vor dem Zerstören auszuführen, gibt es die "Destruktor-Funktion" DESTROY. Im Gegensatz zu new ist dieser Name festgelegt.
Besondere Aktivitäten sind zum Beispiel ein geöffnetes File oder eine geöffnete Datenbankanbindung schließen. Wird nämlich zu einer konkreten Instanz eine Datei geöffnet, sollte diese auch wieder geschlossen werden, bevor die Instanz zerstört wird.
Damit können wir jetzt eine konkrete Instanz eines Objekts erzeugen und zerstören (lassen). Jedoch hat diese Instanz weder irgendwelche Eigenschaften noch Methoden (außer new).
Methoden
Methoden sind Funktionen, die zu einem Objekt gehören. Das heißt, sie sind in der zugehörigen Klasse definiert und können von der Instanz benutzt werden. Genauso wie bei new wird auch bei jeder anderen Methode ein erstes Argument "automatisch" übergeben. Der Unterschied dazu: Das erste Argument ist nicht der Klassenname, sondern die Referenz auf den in new erzeugten Hash - also die aktuelle Instanz. Als nächstes können weitere Argumente folgen.
Hier die Erweiterung obiger Klasse um drei Methoden:
package smallCGI;
sub new {
my $self=shift;
my $ref={};
bless($ref, $self); # nicht &bless schreiben!
$ref;
}
sub printHead {
my $self=shift;
print "<html>\n<head>\n</head>\n<body>\n";
}
sub printParagraph {
my $self=shift;
my $text=shift;
print "\n<p>\n";
print $text;
print "\n</p>\n";
}
sub printEnd {
my $self=shift;
print "\n</body>\n</html>\n";
}
1;
Das fällt auf:
- Die drei neuen Funktionen printHead, printParagraph und printEnd sind geben HTML-Code auf die Standardausgabe.
- Jede Funktion erhält wie bereits erwähnt, eine Referenz auf den speziellen Hash - genauer gesagt die aktuelle Instanz selbst - als Übergabe. Diese wird mit shift ausgelesen (und somit das Übergabearray @_ abgearbeitet), aber hier nicht mehr gebraucht. printParagraph jedoch erwartet ein weiteres Argument, nämlich HTML-Code für einen Absatz im Body einer HTML-Seite.
Der Code des Hauptprogramm sieht z. B. so aus:
use smallCGI;
my $perli=smallCGI->new;
$perli->printHead;
$perli->printParagraph("A simple dynamic HTML-page.\nIt's nice ...");
$perli->printEnd;
Das fällt auf:
- Der Aufruf einer Methode einer Instanz geschieht mit -> - kennen wir schon von new. Jedoch steht links von -> die Instanz der Klasse und nicht der Klassenname.
- Man kann einer Methode Argumente übergeben.
- Die leeren Klammernpaare sind obligatorisch.
Die Ausgabe:
<html>
<head>
</head>
<body>
<p>
A simple dynamic HTML-page.
It's nice ...
</p>
</body>
</html>
Eigenschaften
Eigenschaften einer Instanz sind Variablen und müssen Teil der Instanz sein und nicht Teil der Klasse! Letzteres würde dazu führen, dass mehrere Instanzen einer Klasse auf die gleiche Variable zugreifen würden. Im Konstruktor einer Klasse wird ein Hash erzeugt, der weiß, zu welcher Klasse er gehört. Nun ist der Hash nicht nur dazu da, um der Instanz zu sagen, wo es hingehört, sondern man kann in ihm auch Schlüssel-Wert-Paare (dazu ist er eigentlich gedacht) speichern. Damit haben wir den Platz, den wir benötigen, um Eigenschaften abzuspeichern.
Die Eigenschaften AUTHOR und DATE sind neben weiteren Funktionen hinzugefügt worden:
package smallCGI;
sub new {
my $self=shift;
my $ref={};
bless($ref, $self); # nicht &bless schreiben!
$ref->{DATE}=&htmlMetaTime;
$ref;
}
sub printHead {
my $self=shift;
print "<html>\n<head>\n";
if ($self->{AUTHOR}) {
print ' <meta name="author" content="';
print $self->{AUTHOR};
print "\">\n";
}
if ($self->{DATE}) {
print ' <meta name="date" content="';
print $self->{DATE};
print "\">\n";
}
print "</head>\n<body>\n";
}
sub printParagraph {
my $self=shift;
my $text=shift;
print "\n<p>\n";
print $text;
print "\n</p>\n";
}
sub printEnd {
my $self=shift;
print "\n</body>\n</html>\n";
}
sub setAuthor {
my $self=shift;
$self->{AUTHOR}=shift;
}
sub htmlMetaTime {
my @time=localtime(time);
&format($time[5]+1900,4)."-".&format($time[4]+1,2)."-".&format($time[3],2).
"T".&format($time[2],2).":".&format($time[1],2).":".&format($time[0],2).'+01:00';
}
sub format {
my $i; my $res="";
for ($i=$_[1]-length($_[0]); $i>0; $i--) { $res.="0"; }
$res.$_[0];
}
1;
Erklärung:
- Im Konstruktor wird der Referenz, die zurückgegeben wird der Schlüssel DATE mit dem Wert des Ergebnisses von &htmlMetaTime hinzugefügt.
- Die Methode printHead gibt jetzt auch die Metaangaben AUTHOR und DATE mit aus (falls diese existieren). Dabei wird auf die aktuelle Instanz ($self) zugegriffen.
- Die Methode setAuthor erwartet ein Argument - nämlich den Namen des Autors. Dieser Name wird in den Hash unter dem Schlüssel AUTHOR geschrieben.
- Die Methoden htmlMetaTime und format sind nur dazu da, die aktuelle Zeit als Metaangabe zu berechnen.
use smallCGI;
my $perli=smallCGI->new();
$perli->setAuthor("Hans Mueller, Hintertupfing");
$perli->printHead;
$perli->printParagraph("A simple dynamic HTML-page.\nIt's nice ...");
$perli->printParagraph("Der Autor ist ".$perli->{AUTHOR}.".");
$perli->printEnd;
Und das fällt auf:
- Man kann direkt auf den Inhalt des Hashes (Instanz) - also auf die Eigenschaften des Objekts - zugreifen: $perli->{AUTHOR}. Es ist aber üblich und auch guter Programmierstil über eine Methode eine Eigenschaft auszulesen oder zu setzen.
<html>
<head>
<meta name="author" content="Hans Mueller, Hintertupfing">
<meta name="date" content="2002-06-06T13:41:35+01:00">
</head>
<body>
<p>
Der Autor ist Hans Mueller, Hintertupfing.
</p>
</body>
</html>
Polymorphie
In Perl ist es möglich mehrere Konstruktoren zu schreiben, da ja ein Konstruktor verschiedene Namen haben kann. Diese Konstruktoren können völlig verschieden sein. Die dadurch geschaffene Möglichkeit völlig verschiedene Objekte einer Klasse zu erzeugen, nennt man Polymorphie.