Werbung einblenden Werbung ausblenden


Home / Tutorials / Perl Handbuch / objektorientierte Programmierung

Was ist objektorientierte Programmierung?
Was ist ein Objekt?
Wozu gehört das Objekt? - bless Funktion
Den Zeiger auf ein Objekt einer Subroutine übermitteln - shift Funktion
Eine alternative Möglichkeiten, den Zeiger auf ein Objekt zu übermitteln - $_[0]
Entwicklung eines neuen Objektes, das die Subroutinen der Klasse übernimmt, eine Subroutine allerdings modifiziert (überschreibt)
Entwicklung eines neuen Objektes, das die Subroutinen der Klasse übernimmt, eine Subroutine überschreibt und eine neue einführt
Der Zeiger des Objektes wird der Funktion nicht übermittelt
Der Zeiger des Objektes wird der Funktion als erster Parameter übergeben

Was ist objektorientierte Programmierung?

Selbst wer nicht vorhat in Perl objektorientiert zu programmieren, sollte sich dieses Kapitel unbedingt anschauen. Die meisten Perlmodule, wie z.B. das gd Modul oder das CGI Modul sind objektorientiert programmiert. Ein Grundverständnis der objektorientierten Programmierung erleichtert das Arbeiten mit diesen Modulen. Die objektorientierte Programmierung wird am einfachsten verständlich, wenn man sich mit den Beispielen in diesem Kapitel eingehend beschäftigt.

Bücher über objektorientierte Programmierung beginnen meistens mit irgendwelchen Waschmaschinen oder Autos. Es wird erklärt, dass z.B. eine Waschmaschine eine Sammlung von Eigenschaften und Methoden ist. Eine Waschmaschine pumpt Wasser, erhitzt Wasser, schleudert, trocknet etc.. Von dieser abstrakten Waschmaschine gibt es dann Instanzen, das sind dann konkrete Objekte der Klasse Waschmaschine, die im Haushalt stehen und alle Eigenschaften der Klasse, also der abstrakten Definition von Waschmaschine erben. Ein Objekt ist ein Bauteil, das man nicht versteht und nicht verstehen muss. Entscheidend ist, was reinkommt, wie man es anschließt und was rauskommt. Objekt haben in der Programmierung den Vorteil, dass sich größere, immer wieder auftauchende Probleme zu einem kleinen Softwarepaket (package) zusammenschnüren lassen und dann lediglich aufgerufen werden müssen.

Was ist ein Objekt?

Die schnellste Methode zu verstehen was ein Objekt ist, ist die Programmierung eines Objektes. Das machen wir jetzt und erklären dabei step by step.

package rechner;

sub new 
{
     $zeiger={};
    print $zeiger."\n";
   bless($zeiger);
}

sub werte_setzen 
{
    $nirvana=shift;
    print $nirvana. "\n";
    $nirvana->{'zahl1'}=shift;
    $nirvana->{'zahl2'}=shift;
}

sub rechnen
{
    $nirvana=shift;
    $summe=$nirvana->{'zahl1'} + $nirvana->{'zahl2'};
}

$mein_erstes_Objekt=rechner->new();
$mein_erstes_Objekt ->werte_setzen(30,50);
print $mein_erstes_Objekt->rechnen()."\n";

Dieses Skript liefert drei Wert

a) print $zeiger;

=>HASH(0x176f120)

b) print $nirvana;

=>rechner=HASH(0x176fid4)

c) print mein_erstes_Objekt->rechnen();

=>80


Das Kapitel Referenzen sollte man jetzt in Erinnerung haben, insbesondere die anonymen Hashs bzw. Arrays.
Die stimmigste Definition eines Objektes in Perl stammt von Larry Wall selbst.
Ein Objekt ist ein "referenziertes Dingsda", das weiß zu wem es gehört.

Es stellen sich also zwei Fragen:
1.) Wer ist das "referenzierte Dingsda"?
2.) Zu wem gehört es?
Das "referenzierte Dingsda" ist im Beispiel der Zeiger auf den anonymous Hash (Position a). Dingsda steht für irgendetwas. Es könnte genau so gut ein Zeiger auf einen Array oder auf eine Variable sein. Bei einem Zeiger auf eine simple Variable hätten wir allerdings Probleme, beliebig viele Werte zuzuweisen.

Wozu gehört das Objekt? - bless Funktion

Wenn die new Funktion aufgerufen wird, wird erst mal eine Referenz gebildet. Dann kommt die bless Funktion, damit das Dingsda weiß, zu wem es gehört. Wir sehen an diesem Beispiel genau, dass wir zuerst nur eine Referenz haben. Nachdem die bless Funktion ausgeführt worden ist, weiß das "referenzierte Dingsda" zu wem es gehört. Es gehört dann nämlich zum Paket (package) rechner. rechner wiederum ist ein Package, das Eigenschaften und Methoden hat.
Das "referenzierte Dingsda" ist eine Instanz von diesem Package und hat folglich alle Eigenschaften, die das Package hat.

Der Zeiger auf ein Objekt einer Subroutine übermitteln - shift Funktion

Schauen wir uns das Beispiel oben noch mal genauer an und klären shift und $nirvana.
Das package rechner, von dem das Objekt ("referenziertes Dingsda") eine Instanz ist, hat drei Subroutinen: new, werte_setzten und rechnen.
In new passiert alles, was wir tun müssen, um eine Instanz dieses Packages zu bilden. Es wird eine Referenz auf einen anonymous Hash gebildet. Das heißt, hier wird nur festgelegt, auf was wir eine Referenz bilden (anonymous Hash/-Array oder Variable). Diese Referenz wird später allerdings überschrieben, sonst könnte man nicht mehrere Objekte desselben packages bilden. (Vergleich: HASH(0x176f120) mit rechner=HASH(0x176fid4)).
Die bless Funktion ordnet nun dem referenzierten Dingsda das dazugehörige Package zu. Die Zeile $zeiger={} kann man auch weglassen, da der anonymous Hash der Default ist.
Ist das referenzierte Dingsda ein anonymous Array, schreibt man $zeiger=[ ]. (Beispiel unten)
Dem anonymous Hash aus der Subroutine new weisen wir in der Funktion werte_setzen Werte zu. Es ist klar zu wem dieses Objekt gehört nämlich zum package rechner.

$mein_erstes_Objekt hat jetzt also den Wert rechner=HASH(0x176fid4). Da das package rechner die Subroutinen werte_setzen und rechnen hat, kann man diese jetzt aufrufen. Dies passiert mit dem bekannten Dereferenzierungsoperator z.B. $mein_erstes_Objekt->werte_setzen bzw. $mein_erstes_Objekt->rechnen.
Es kann beliebig viele Instanzen eines Packages geben, also unendlich viele Objekte, die alles können, was das Package kann. Jedes von diesen Objekten kann seine eigenen Werte haben. Dies legen wir für jedes Objekt in der Subroutine werte_setzen fest. Wenn jetzt ein bestimmtes Objekt die Subroutine rechnen ausführt, soll das mit den Werten des entsprechenden Objektes passieren.

Die Frage ist, woher weiß die Subroutine rechnen, welche Werte sie jetzt verwenden soll?
Bei jedem Aufruf einer Subroutine wird der Zeiger auf das Objekt, also in unserem Falle rechner=HASH(0x176fid4) mitgeliefert. Das ist der Wert, den das Programm braucht um zu funktionieren. Diesen Wert fangen wir mit shift ab (siehe unten)
$mein_erstes_Objekt ->werte_setzen(30,50); sieht eigentlich so aus:
$mein_erstes_Objekt ->werte_setzen(rechner=HASH(0x176fid4), 30, 50);
Der erste Wert wird immer mitgeliefert. Diesen fangen wir ab, denn er zielt auf unseren anonymous Hash. Es ist der Wert, der mit new generiert wurde und an die Variable $mein_erstes_Objekt übergeben wurde.
Ruft mein erstes Objekt eine Subroutine auf, wird dieser Wert wieder mitgeliefert. Perl weiß jetzt, wer zu wem gehört.

Warum funktioniert $nirvana=shift;? Wir erinnern uns (Funktionen zur Bearbeitung von Arrays), dass die Funktion shift das erste Element eines Arrays entfernt, und das entfernte Element zurückliefert. Die Syntax ist shift (@irgendein_array). Hier steht aber nur $nirvana=shift;. Wir erinnern uns, dass die Parameter bei dem Aufruf einer Subroutine (Subroutinen) in dem Spezialarray @_ landen. Die shift Funktion wird auf diesen Sonderarray angewendet, wenn kein anderer Array angegeben wird.

Eine alternative Möglichkeiten, den Zeiger auf ein Objekt zu übermitteln - $_[0]

Anstelle $nirvana=shift; ist auch folgende Schreibweise möglich:

package rechner;
sub new 
{
     $zeiger={};
     bless($zeiger);
}

sub werte_setzen 
{
    $nirvana=$_[0];
    $nirvana->{'zahl1'}=$_[1];
    $nirvana->{'zahl2'}=$_[2];
}

sub rechnen
{
   $nirvana=$_[0];

    $summe=$nirvana->{'zahl1'} + $nirvana->{'zahl2'};
}

$mein_erstes_Objekt=rechner->new();
$mein_erstes_Objekt ->werte_setzen(30,50);
print $mein_erstes_Objekt->rechnen()."\n";

Die Funktion werte_setzen wurde modifiziert. Es wird direkt mit dem Sonderarray @_ gearbeitet. $_[0] beinhaltet den Zeiger, den Perl braucht. $_[1] ist die 30 und $_[2] die 50.
Wer mit einem anonymen Array, anstatt mit einem anonymen Hash arbeiten will, programmiert wie im folgenden

Beispiel:

package rechner;

sub new 
{
     $zeiger=[ ];
     bless($zeiger);
}

sub werte_setzen 
{
    $nirvana=$_[0];
    $nirvana->[0]=$_[1];
    $nirvana->[1]=$_[2];
}

sub rechnen
{
   $nirvana=$_[0];
   $summe=$nirvana->[0] + $nirvana->[1];
}

$mein_erstes_Objekt=rechner->new();
$mein_erstes_Objekt ->werte_setzen(60,70);
print $mein_erstes_Objekt->rechnen()."\n";

Bless wird hier auf einen anonymous Array angewendet. Damit stecken die zu diesem Objekt gehörenden Variablen natürlich auch in $zeiger->[1] bzw. in $zeiger->[2].
Wer will, kann auch wie folgt dereferenzieren:

package rechner;

sub new 
{
     $zeiger=[ ];
     bless($zeiger);
}

sub werte_setzen 
{
   $nirvana=$_[0];
   $$nirvana[0]=$_[1];
   $ $nirvana[1]=$_[2];
}

sub rechnen
{
    $summe=$$nirvana[0] + $$nirvana[1];
}

$mein_erstes_Objekt=rechner->new();
$mein_erstes_Objekt ->werte_setzen(60,70);
print $mein_erstes_Objekt->rechnen()."\n";

print $mein_erstes_Objekt->rechnen(); gibt als Ergebnis 130 zurück.

Entwicklung eines neuen Objektes, das die Subroutinen der Klasse übernimmt, eine Subroutine allerdings modifiziert (überschreibt)

Es kann der Fall eintreten, dass bei einem Objekt bestimmte Methoden nicht von der Klasse übernommen werden sollen oder dass die Klasse überschrieben werden soll. Genauso denkbar ist auch, dass ein Objekt eine Subroutine haben soll, die in dem Package nicht enthalten ist. Man schreibt also ein neues Package (package rechnerzwei;), dass bestimmte Methoden ändert oder hinzufügt. Alle anderen Methoden werden vom ersten Packege übernommen (package rechner;). Man spricht hier von Vererbung.

Beispiel:
package rechner;

sub new 
{
     $der_name_des_packets=$_[0];     
     $zeiger=[ ];
     bless($zeiger,$der_name_des_packets);
}

sub werte_setzen 
{
   $nirvana=$_[0];
   $$nirvana[0]=$_[1];
   $$nirvana[1]=$_[2];
}

sub rechnen
{
    $nirvana=$_[0];
    $summe=$$nirvana[0] + $$nirvana[1];
}

package rechnerzwei;

@ISA="rechner";

sub rechnen
{
   $nirvana=$_[0];
   $summe=$$nirvana[0]  *  $$nirvana[1];
}

$mein_zweites_objekt=rechnerzwei->new();
$mein_zweites_objekt->werte_setzen(5,6);
print $mein_zweites_objekt->rechnen();

Bei diesem Skript fällt auf, dass sich die bless Funktion geändert hat. Es wird jetzt nicht mehr nur ein Wert übergeben sondern auch der Name des Packages. Bisher war das nicht nötig, weil es nur ein Package gab. Es gibt nur eine new Subroutine, die das Objekt initialisiert, aber zwei Packages.
Gibt es nur ein Package, dann bindet die bless Funktion das "referenzierte Dingsda" an das Package, das die bless Funktion enthält. Das wäre in diesem Falle package rechner. Wir wollen aber mit der Subroutine rechnen von package rechnerzwei arbeiten. Sie soll jetzt multiplizieren und nicht addieren. Also müssen wir es schaffen, eine Referenz zu bekommen, die mit dem package rechnerzwei verknüpft ist. Damit das gelingt, muss die Subroutine new wissen zu welchem Package eine Zuordnung erfolgen soll.
Das ist kein Problem, da bei Aufruf der new Subroutine in ein_zweites_objekt=rechnerzwei->new(); der Name des aufrufenden Packages mit übergeben wird. Diesen fangen wir in der new Subroutine ab und packen ihn in die Variable $der_name_des_packets.
Die bless Funktion können wir dann mit zwei Parametern aufrufen, der Referenz und dem Namen des Packages, an das die Referenz gebunden werden soll (rechnerzwei). $mein_zweites_objekt gehört jetzt zum Package rechnerzwei.

Package rechnerzwei soll nicht nur seine eigenen Methoden erkennt, sondern auch die Subroutinen von rechner. Der Vererbungsmechanismus ist in Perl durch den @ISA realisiert. Wird eine Subroutine aufgerufen, die Perl in dem aktuellen Package nicht findet, dann schaut er in @ISA zwei nach, welche Packages dort eingetragen sind. Perl versucht dann die entsprechende Subroutine in einem der dort genannten Packages zu finden. Wird die entsprechende Subroutine gefunden, wird sie ausgeführt. Wir müssen also das Package rechner in den Array @ISA eintragen. Das passiert in dieser Zeile.
@ISA="rechner";

Entwicklung eines neuen Objektes, das die Subroutinen der Klasse übernimmt, eine Subroutine überschreibt und eine neue einführt

Nach dem gleichen Schema werden Subroutinen nicht nur überschrieben, sondern auch neue eingeführt.

package rechner;

sub new 
{
     $der_name_des_packets=$_[0];     
     $zeiger=[ ];
     bless($zeiger,$der_name_des_packets);
}

sub werte_setzen 
{
   $nirvana=$_[0];
   $$nirvana[0]=$_[1];
   $$nirvana[1]=$_[2];
}

sub rechnen
{
    $nirvana=$_[0];
    $summe=$$nirvana[0] + $$nirvana[1];
}

package rechnerzwei;

@ISA="rechner";

sub rechnen
{
   $nirvana=$_[0];
   $$nirvana[2]=$$nirvana[0]  *  $$nirvana[1];
}
sub auf_den_schirm_setzen
{
  $nirvana=$_[0];
  print "Das Ergebnis der Multiplikation von $$nirvana[0] mit $$nirvana[1] ergibt $$nirvana[2]";
}

$mein_zweites_objekt=rechnerzwei->new();
$mein_zweites_objekt->werte_setzen(5,6);
$mein_zweites_objekt->rechnen();
$mein_zweites_objekt->auf_den_schirm_setzen();
Der Zeiger des Objektes wird der Funktion nicht übermittelt

Diskutieren wir folgendes Beispiel:

package rechner;

sub new
{
   bless{};
}
sub rechnen
{
     $zahl1=$_[1];
     $zahl2=$_[2];
     $summe=$zahl1+$zahl2;
}
$objekt=rechner->new();
print $objekt->rechnen(30,50);

Vordergründig sieht das nach objektorientierter Programmierung aus. Ist es aber nicht! Die Parameter der Funktion rechnen, gehören nicht zu dem Objekt und man könnte sie innerhalb des Objektes auch nicht mehr aufrufen.

Auch im folgenden Beispiel liegt keine objektorientierte Programmierung vor!

package rechner;

sub new
{
   bless{};
}
sub rechnen
{
     $zahl1=$_[1];
     $zahl2=$_[2];
     $summe=$zahl1+$zahl2;
}
sub zeigen
{
    print $summe;

}
$objekt1=rechner->new();
$objekt1->rechnen(30,50);
$objekt2=rechner->new();
$objekt2->rechnen(90,60);
$objekt1->zeigen();
$objekt2->zeigen();

Wir wollen eigentlich 80 und 150 auf dem Bildschirm sehen, erhalten aber zweimal 150. Das heißt, die Variable $summe, die zu Beginn 80 war, wird überschrieben durch 150. Anschließend rufen wir zweimal die Funktion zeigen auf. Wir erhalten zweimal den Wert 150. $summe ist hier keine Instanz Variable sondern eine normale, global gültige Variable. $summe gehört nicht zu einem Objekt.

Der Zeiger des Objektes wird der Funktion als erster Parameter übergeben

Das folgende Beispiel ist objektorientiert programmiert!
Hier wird die Referenz auf das Objekt den Subroutinen als erster Paramater mit übergeben.

package rechner;

sub new
{
  $zeiger=[ ];
   bless($zeiger);
}
sub rechnen
{
     $nirvana=$_[0];
     $$nirvana[0]=$_[1];
     $$nirvana[1]=$_[2];
     $$nirvana[2]=$$nirvana[0]+$$nirvana[1];
}
sub zeigen
{
    $nirvana=$_[0];
    print $$nirvana[2];

}
$objekt1=rechner->new();
$objekt1->rechnen(30,50);
$objekt2=rechner->new();
$objekt2->rechnen(90,60);
$objekt1->zeigen();
$objekt2->zeigen();

Hier kommt raus, was wir erwarten: 80 und 150. Machen wir uns das nochmal klar. Wir bauen in der Subroutine new eine Referenz auf einen anonymous Array und mit bless weisen wir ihn dem Package rechner zu. $objekt1 und $objekt2 sind jetzt also vom Typ rechner=ARRAY(Hyroglyphen). Wir haben eine Referenz auf einen Array, dem wir Werte zuweisen können und zwar für jedes Objekt einen anderen. Dann rufen wir die Funktion rechnen auf. Die Referenz wird als erster Paramater mit übergeben. Wir könnten sie mit shift auslesen oder mit $_[0].

Unsere Referenz dereferenzieren wir jetzt mit $$nirvana und weisen die entsprechenden Werte zu. $$nirvana hat für jedes Objekt einen anderen Wert.
Wenn wir die Funktion zeigen aufrufen, dann übergeben wir auch wieder die dazugehörige Referenz. Also die gleiche, die wir schon übergeben haben, als wir rechner aufgerufen haben und die gleiche, die am Anfang bei Aufruf der Subroutine new produziert wurde. Jetzt kann die Summe des ersten Objektes von der Summe des zweiten Objektes unterschieden werden.
Hier wurde im Gegensatz zum Beispiel davor tatsächlich objektorientiert programmiert. Dieser Zusammenhang ist wichtig. Viele Module lesen erst mal die Werte ein. (z.B. GD:chart, ein Modul zur graphischen Aufbereitung von statistischen Daten als Torten, Balken oder Punktdiagramm) Erst wenn alle Werte eingelesen sind, werden sie verarbeitet. Das geht nur, wenn das Einlesen der Daten wirklich objektorientiert erfolgt. Das heißt, dass die Daten tatsächlich einem Objekt zugeordnet sind.
Ein etwas komplexeres Beispiel zur objektorientierten Programmierung gibt es unter Gästebuch: Die objektorientierte Variante
vorhergehendes Kapitel