Wenn man Larry Wall Glauben schenken darf, dann ist die Faulheit eine
Tugend des Programmierers.
Und Faulheit ist der beste Antrieb, Perl objektorientiert (OO) einzusetzen,
denn langfristig ist die Arbeitsersparnis enorm.
Da die objektorientierte Programmierung (OOP) eine Wissenschaft für sich ist,
beansprucht sie auch, alles und jeden mit eigenen Bezeichnungen zu versehen, die auf einen Neuling in der OOP natürlich verwirrend wirken mögen.
Ich habe versucht, diese Bezeichnungen auf möglichst viel Bekanntes zurückzuführen.
Im Folgenden sollen die Begriffe
mit Leben in Perl gefüllt werden.
Häufig treten beim Programmieren im Quelltext sich wiederholende Aufgaben auf. Anstatt nun per Copy&Paste den notwendigen Code immer und immerwieder im Programm zu verteilen, bietet sich hier die Unterprogrammtechnik an. Funktionen und Prozeduren sind jedem Programmierer bekannt und erlauben das "Ausklammern" von bestimmten Aufgaben des Algorithmus.
Eine weitere Hilfe stellen zusammengesetzte ("komplexe") Datentypen dar. In Perl existiert zwar kein "RECORD" Datentyp, um mit benannten Elementen arbeiten zu können, das macht aber nichts, dafür lassen sich Hashes und Hashreferenzen benutzen (perldoc perldsc):
my $preis = { betrag => 14.9, waehrung => 'DEM' };
$preis ist jetzt eine Referenz auf einen anonymen Hash, im Nachfolgenden "Objekt" genannt, mit den Elementen "betrag" und "waehrung", auf die beispielsweise mit dem Pfeiloperator zugegriffen werden kann (perldoc perlop):
$preis->{waehrung} = 'EUR';
Und schon haben wir einen schönen Y2002 Bug programmiert ;-) Im weiteren Text werden die Datenelemente eines Objektes "Attribute" genannt.
Objekte koennen natuerlich nicht nur, wie in unserem Beispiel, "von Hand" erzeugt werden, sie koennen genausogut Rueckgabewerte einer Funktion sein. Denkbar ist z.B eine Funktion, die nichts weiter tut, als ein Objekt zu erzeugen und als Rückgabewert zurückzugeben:
sub neues_objekt_fuer_preis { my ($betrag, $waehrung) = @_; my $self = {}; $self->{betrag} = $betrag; $self->{waehrung} = $waehrung; return $self; }
damit wuerde unser ursprunglicher Code so aussehen:
my $preis = neues_objekt_fuer_preis( 14.9, 'DEM');
Solche objekterzeugenden Funktionen nennt man "Konstruktor", die erzeugten Objekte verhalten sich wie Records und sind von gleichem Recordtyp. Der Konstruktor ist also eine Schablone zum Erzeugen von Objekten eines bestimmten Typs. Wozu aber sollte man solche Konstruktoren gebrauchen können wollen? Nun, unser Kontruktor könnte beispielsweise gleich überprüfen, ob die eingegebene Währung zu denen gehört, mit denen das zukünftige Programm umgehen kann, denn ansonsten würde das erzeugte Objekt zu einem Fehler später im Programm führen. Den Prüfkonstruktor benennen wir um von "neues_objekt_fuer_preis" um in "new" und spendieren zusätzlich ein eigenes Package:
package Preis; my %pruefhash; my @gueltige = qw(DEM EUR USD GBP YEN); @pruefhash{@gueltige} = (0) x scalar( @gueltige ); sub new { my ($betrag, $waehrung) = @_; exists( $pruefhash{ $waehrung } ) || die "Waehrung $waehrung kann nicht verabeitet werden"; my $self = {}; $self->{betrag} = $betrag; $self->{waehrung} = $waehrung; return $self; } 1;
aufgerufen durch
use Preis; my $preis = Preis::new( 14.9, 'DEM');
Die Umbenennung unseres Konstruktors war nicht notwendig, per Konvention werden
Konstruktoren aber im allgemeinen "new" benannt.
Die Aulagerung in in Package wurde vorgenommen, weil noch weitere Funktionen
hinzukommen und in diesem Package gesammelt werden sollen.
Bis jetzt haben wir nur über zusammengesetzte Datentypen gesprochen und für
bekannte Dinge komische Namen eingeführt.
Machen wir nun den entscheidenden Schritt zur Objektorientierten Programmierung und fügen Unterprogrammtechnik und komplexe Datentypen zusammen:
unser Objekt soll nicht nur Attribute enthalten, sondern auch Funktionen,
die auf den Attributen operieren.
Gewünscht sei z.B. eine Funktion waehrung_ausfuehrlich(), die
zu einem Objekt vom Typ Preis den ausgeschriebenen Namen der Währung ermittelt.
Nach traditioneller Methode würde man Funktion mit Parameter vom Typ
Preis schreiben, objektorientiert ist die Vorgehensweise anders:
der Typ und damit das Objekt besitzt eine eigene Funktion, die sich um diese Aufgabe kümmert:
print $preis->waehrung_ausfuehrlich();
Auf die Funktionen eines Objektes kann man also genauso zugreifen, wie auf
die Attribute: mit dem Pfeiloperator.
Einfach, nicht?
Natürlich haben auch diese an das Objekt gebundenen Funktionen einen spezielle Namen:
man spricht von "Methoden".
Wie aber kommt das Objekt nun zu seinen Methoden?
das Geheimnis ist die Funktion "bless".
Diese Funktion tut nichts weiter, als ein Objekt mit einem Packagenamen zu verknüpfen.
Nach dem blessen schlägt Perl alle Methodennamen, die mit
$objekt->methodenname();
aufgerufen werden,
in genau dem mit bless hinterlegten Package nach.
Damit wäre geklärt, wie die richtige Methode gefunden wird.
Aber woher weiss die Methode, welches Objekt sie bearbeiten soll?
Hier macht Perl folgendes:
als erstes Element des Parameterarrays übergibt Perl
eine Referenz auf das Objekt, dessen Methode gerade aufgerufen wird.
Der Programmierer kann nun mit bspw.
my $self = shift;
auf diese Referenz zugreifen und damit über das Objekt verfügen. In unseren Beispiel sieht das so aus :
package Preis; use strict; my %pruefhash; my @gueltige = qw(DEM EUR USD GBP YEN); @pruefhash{@gueltige} = (0) x scalar( @gueltige ); my %ausfuehrliche_namen; @ausfuehrliche_namen{@gueltige} = ('D-Mark', 'Euro', 'US Dollar', 'Britisches Pfund', 'Yen'); #---------------------------------------------- sub new {
my $pkg = shift;
my ($betrag, $waehrung) = @_; exists( $pruefhash{ $waehrung } ) || die "Waehrung $waehrung kann nicht verabeitet werden"; my $self = {}; $self->{betrag} = $betrag; $self->{waehrung} = $waehrung;
bless $self, $pkg;
return $self; } #---------------------------------------------- sub waehrung_ausfuehrlich {
my $self = shift;
return $ausfuehrliche_namen{ $self->{waehrung} }; } #---------------------------------------------- 1;
aufgerufen mit
use Preis; my $preis = Preis->new( 14.9, 'DEM'); print $preis->waehrung_ausfuehrlich();
Noch nicht betrachtet wurde die Variable $pkg:
in Ihr wird der Packagename übergeben.
Beim Thema "Vererbung" wird diese zunächst merkwürdig
wirkende Vorgehensweise begründet.
Unser Package enthält nun Konstruktor und zugehörige Methoden.
Diese Gesamtheit von Konstruktor(en) und Methoden, die zu einem Typ gehören, nennt man "Klasse".
Ein vom Konstruktor einer Klasse C erzeugtes Objekt nennt man auch
Instanz der Klasse C.
Wir haben bereits gesehen dass der direkte Zugriff auf Attribute, wie ihn
$preis->{waehrung} = 'EUR';
darstellt, zu Problemen führen kann, denn in diesem Fall wird die Währung zwar geändert,
der Betrag aber nicht angepasst.Igendjemand ist nun unberechtigt reicher oder ärmer geworden
und darauf aus, den Entwickler der Schrottsoftware in die die Finger zu bekommen...
Darum wendet die OOP folgendes Prinzip an:
Dieses Prinzip trägt den Namen "Kapselung"
Unsere Methode zum Setzen der Währung soll die Umrechnung des Betrages in die neu Währung gleich mit erledigen und setzt die Funktion wechselkurs() voraus, die den Wechselkurs der beiden Währungen bestimmt:
sub setWaehrung { my $self = shift; my ($waehrung_neu) = @_; $self->{betrag} = $self->{betrag} * wechselkurs( $self->{waehrung}, $waehrung_neu ); $self->{waehrung} = $waehrung_neu; }
der Zugriff auf das Attribut 'waehrung' kann jetzt mit
$preis->setWaehrung('EUR');
erfolgen.
Methoden wie "setWaehrung", deren Hauptaufgabe darin besteht, Attribute zu setzen oder zu lesen,
werden "Property-Methoden" genannt.
Die Benamsung erfolgt üblicherweise nach dem Schema (get|set)Attributname.
Vorteil der Nutzung von Propertymethoden ist, daß der Zugriff auf die
Attribute über genau eine Schnittstelle erfolgt. Seiteneffekte, speziell bei
Erweiterungs- oder Wartungsprogrammierarbeiten, bleiben beherrschbar.
Obwohl Perl, im Gegensatz zu bspw. C++ oder Java mit der "private" Deklaration, keine Möglichkeit bietet,
die Attribute dem direkten Zugriff zu entziehen und damit die Kapselung zwingend durchzusetzen, sollte
man als Programmiere jedoch immer die Kapselung einhalten, um unangenehme bis üble Effekte zu vermeiden.
Niemand wird sich freillig in den Fuss schiessen, nur weil es kein Gesetz dagegen gibt.