
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.
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.
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();
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
|
 |