Perl Seite #2: Objektorientierte Programmierung in Perl leicht gemacht


English Version

Jede Anwendung der hier beschriebenen Hinweise und Tips erfolgt ausschließlich auf eigene Gefahr. Keine Haftung für die Richtigkeit der Darstellung. Jegliche Haftung für irgendwelche Schäden aufgrund dieses Textes ist ausgeschlossen.

Achtung! Dieser Text wurde von einem Hobbyprogrammierer für Hobbyprogrammierer erstellt, die schnell zu lauffähigen Skripten gelangen möchten.
Den Ansprüchen von Profi-Programmierern oder Informatikstudenten könnte der Text, insbesondere im Hinblick auf eine wissenschaftlich exakte Verwendung der Begriffe der Objektorientierten Programmierung, möglicherweise nicht genügen.


Inhalt:

  1. 1. Einleitung
  2. Klassen als Definitionen von Containern
  3. Instantiierung eines Objekts
  4. Ein erstes Beispiel-Skript
  5. Übergabe von Argumenten; Ausweitung des Beispiels
  6. Virtuelle Abbildung von realen Gegenständen
  7. Verarbeitung von Attributen
  8. Hashsyntax bei der Argumentübergabe im Rahmen der Objektinstantiierung
  9. Was "$self->{attribute}" eigentlich bedeutet
  10. Arrays und Hashes als Attribute in einer Klasse
  11. Übergabe von Objekten an Funktionen
  12. Vererbung
  13. OOP-Syntax in Perl/Tk
  14. Interaktion von Objekten und Klassendesign
  15. Ein längeres Programmbeispiel


1. Einleitung

Objektorientierte Programmierung (OOP) scheint in Perl (Perl 5) für viele Programmierer immer noch nicht leicht zu verstehen zu sein, so daß sie lieber vermeiden, Perl-Skripte auf diese Weise zu schreiben.

Dabei ist es gar nicht so schwer, wenn man weiß, wohin das alles am Ende führen soll und man sich auf dieses Ziel konzentriert, anstatt sich in Einzelheiten der Implementierung zu verlieren.

Im folgenden geht es um die in Perl 5 eingebaute Möglichkeit zur OOP. Manche verwenden stattdessen das Modul "Moose", um das es hier jedoch nicht geht.

Mit der folgenden Darstellung kann man viele Problemstellungen lösen. Dennoch werden nicht alle Fragen der OOP behandelt und möglicherweise werden die OOP-Begriffe z.B. für die Ansprüche von Informatikstudenten nicht immer präzise genug verwendet.
Weiterführende Texte wären z.B. "perldoc perlboot", "perldoc perltoot" oder "perldoc perlootut".
Eine fundierte, aber eben nicht so schnell durchzuarbeitende Gesamtdarstellung bietet das Buch "Intermediate Perl" oder dessen vorherige Version "Learning Perl Objects, References, and Modules" (Kapitel 8 dieses Buches scheint mit "perldoc perlboot" identisch zu sein).
Weiterhin gibt es das noch ausführlichere Buch "Object Oriented Perl", in der deutschen Übersetzung "Objektorientiert Programmieren mit Perl".

Einen ähnlich praxisorientierten Ansatz wie auf dieser Seite findet man noch hier.


2. Klassen als Definitionen von Containern

Eine Klasse kann man im Prinzip als Definition eines Containers verstehen, eines Containers, der Variablen und Funktionen enthält.
Ein Array ist auch ein Container, es kann eine oder mehrere Variablen speichern (oder in Perl 5 z.B. auch andere Arrays).
Eine Klasse kann darüber hinaus auch Funktionen enthalten.
Die in einer Klasse enthaltenen Variablen nennt man "Attribute", die in der Klasse enthaltenen Funktionen nennt man "Methoden".
Der Vorteil daran ist insbesondere, daß in den Methoden alle Attribute der Klasse bekannt sind. Man muß diese den Methoden nicht erst als Argumente übergeben.


3. Instantiierung eines Objekts

In einem Skript benutzt man meist nicht nur ein einziges Array oder einen einzigen Hash, sondern mehrere davon. Genauso kann man auch von den Containern, deren Inhalt durch die Klasse definiert wird, mehrere in einem Skript verwenden. Man definiert erst eine Klasse und gibt dann im Skript die Anweisung, daß man nun einen dieser Container, dessen Inhalt durch die Klasse definiert wird, verwenden möchte.
Daraufhin steht einem ein solcher Container unter dem gewünschten Namen zur Verfügung. Diesen Container nennt man "Objekt", und wie gesagt kann man mehrere dieser Objekte anfordern. Man spricht insoweit von "Instantiierung", man fordert also ein Objekt, das heißt eine Instanz der Klasse, an.


4. Ein erstes Beispiel-Skript

Wie sieht also nun ein Perl-Skript aus, in dem eine Klasse definiert wird, und in dem anschließend ein Objekt dieser Klasse instantiiert wird? Hier ein Beispiel:

#!/usr/bin/perl

# OOP-Beispielskript 1

use warnings;
use strict;

package Lamp;

    sub new {
        my $classname = shift;
        my $self = {state => "off"};
        return bless($self, $classname);
    }

    sub showState {
        my $self = shift;
        print $self->{state};
        print "\n";
    }

package main;

my $lamp = Lamp->new();
$lamp->showState();

---

Bitte einmal das Skript ausführen und (gegebenenfalls mit Verwunderung) feststellen, daß es ohne Beanstandung läuft.

Wie geht das? Eins nach dem anderen: Die Zeile

package Lamp;

sagt - für unsere Zwecke - daß nun eine Klassendefinition folgt und der Name der Klasse "Lamp" sein soll.
Klassennamen schreibt man üblicherweise groß.
Beim Schreiben sollte man den Text der Methoden innerhalb der Klassendefinition wie gezeigt einrücken, sonst wird das Skript schnell unübersichtlich.

Die Methode "new()" ist in jeder Klasse vorhanden. Sie wird ausgeführt, wenn ein Objekt instantiiert wird.
Der seltsame Code in der Methode "new()" braucht uns nicht zu interessieren (ich nenne das "Klassenmagie"). Entscheidend ist, daß wir, wenn wir bei der Objektinstantiierung diese Methode aufrufen, ein Objekt in der Form erhalten, wie wir es haben wollen.
Eine Ausnahme davon ist die Zeile

my $self = {state => "off"};

Zwischen den geschweiften Klammern werden die Attribute der Klasse angegeben. Hier haben wir ein Attribut "state" mit dem Wert "off".

Nach der Methode "new()" folgen die weiteren Methoden, also die weiteren Funktionen innerhalb der Klasse.
Hier haben wir eine Methode "showState()".
Jede Methode in einer Klasse hat "$self" als ersten Parameter. Dies wird entweder durch folgende Zeile am Anfang der Methode ausgedrückt:

my $self = shift;

Oder aber, wenn die Methode mehrere Parameter hat, durch eine Zeile wie:

my ($self, $second_parameter, $third_parameter) = @_;

Durch "$self" als ersten Parameter wird angezeigt, daß die Methode zur Klasse gehört, und auf diese Weise werden der Methode die Attribute der Klasse zur Verfügung gestellt.
Obwohl das "shift()" in der Zeile oben darauf hindeutet, daß der Methode ein Argument übergeben wurde, ist es nicht der Benutzer, der dieses Argument mit dem Funktionsaufruf übergibt. Vielmehr wird in diesem speziellen Fall "$self" durch einen automatischen Mechanismus übergeben.

Sodann kann die Methode die Attribute verarbeiten. Wie gezeigt, hat man innerhalb einer Methode mit der Syntax

$self->{state}

Zugriff auf die Attribute.

Nachdem die Klassendefinition abgeschlossen ist, definiert man entweder eine weitere Klasse, oder man kehrt mit

package main;

in den gewöhnlichen Befehlsbereich (Namensraum) des Skripts zurück.
Mit der Zeile

my $lamp = Lamp->new();

wird dann ein Objekt der Klasse "Lamp" instantiiert. Die Methode "new()" innerhalb der Klasse wird ausgeführt, und anschließend steht in der Variablen "$lamp" ein Objekt der Klasse "Lamp" zur Verfügung.

Mit der Zeile

$lamp->showState();

wird dann die Methode "showState()" in dem $lamp-Objekt aufgerufen. Was daraufhin ausgeführt wird, richtet sich nach der Definition dieser Methode in der Klasse "Lamp". In diesem Skript wird also der Wert des Attributs "state" mit Hilfe von "print" ausgegeben.

Nun haben wir also folgendes erreicht:

Die obige Syntax wird in zahlreichen Perl-Büchern und -dokumenten wie z.B. "perldoc perlobj" verwendet.
Aber tatsächlich lassen sich Klassen ("Packages") auch einfach zwischen geschweifte Klammern schreiben. Dadurch ähnelt der Code viel mehr anderen objektorientierten Sprachen wie C++ oder Java. Ich werde daher ab jetzt diese andere Syntax verwenden:

#!/usr/bin/perl

# OOP-Beispielskript 1

use warnings;
use strict;

package Lamp {

    sub new {
        my $classname = shift;
        my $self = {state => "off"};
        return bless($self, $classname);
    }

    sub showState {
        my $self = shift;
        print $self->{state};
        print "\n";
    }
}

my $lamp = Lamp->new();
$lamp->showState();

5. Übergabe von Argumenten; Ausweitung des Beispiels

Wie anderen Funktionen auch, kann man den Methoden in einer Klasse beim Aufruf Argumente übergeben.
Dies gilt auch für die Methode "new()". Indem man dieser Methode Argumente übergibt, kann man Objekte instantiieren, die unterschiedliche Attribute haben.

Wir erweitern das Lampenbeispiel ein wenig (vgl. Weigend, Python ge-packt, 2. Auflage, 2005, S. 294 f.):

#!/usr/bin/perl

# OOP-example-script 2

use warnings;
use strict;

package Lamp {

    sub new {
        my $classname = shift;
        my $self = {name => shift,
                    lightintensity => shift,
                    state => "off"};
        return bless($self, $classname);
    }

    sub switchOn {
        my $self = shift;
        $self->{state} = "on";
        print "'" . $self->{name} . "' is on at " . $self->{lightintensity} . " Watt.\n";
    }

    sub switchOff {
        my $self = shift;
        $self->{state} = "off";
        print "'" . $self->{name} . "' is off.\n";
    }

    sub newLightBulb {
        my ($self, $light) = @_;
        if ($self->{state} eq "on") {
            print "Light bulb can not be changed. ";
            print "First, '" . $self->{name} . "' has to be switched off.\n";
        } else {
            $self->{lightintensity} = $light;
            print "Light bulb in '" . $self->{name} . "' has been changed. ";
            print "The new bulb has $light Watt.\n";
            $self->switchOn();
        }
    }
}

my $lamp1 = Lamp->new("First Lamp", 50);
my $lamp2 = Lamp->new("Second Lamp", 40);
$lamp1->switchOn();
$lamp2->switchOn();
$lamp2->newLightBulb(100);
$lamp2->switchOff();
$lamp2->newLightBulb(100);

---

Es gibt hier also zwei Objekte "$lamp1" und "$lamp2", die beide durch die Klasse "Lamp" definiert werden. Während "$lamp1" jedoch konstant 50 Watt hat, wird "$lamp2" zunächst mit dem Attribut "40 Watt" instantiiert, danach verändert sich die Wattzahl von "$lamp2" nochmals durch Aufruf der Methode "neueBirne()".

Auch der Methode "newLightBulb()" wird beim Aufruf ein Argument übergeben. Dieses wird mit Hilfe der Funktion "shift()" (die sich ohne Argument bekanntlich auf das Übergabearray "@_" bezieht) der Variablen "$light" zugeordnet. "$light" ist hier eine herkömmliche Variable, die nur innerhalb dieser Methode bekannt ist, aber nicht in den anderen Methoden der Klasse.

In den Methoden können die Attribute nicht nur gelesen, sondern auch neu definiert werden, wie z.B. die Zeile

$self->{state} = "on";
zeigt.

Außerdem sind den Methoden nicht nur die Attribute bekannt, sondern auch die anderen Methoden der Klasse. So ruft die Methode "newLightBulb()" hier nach Wechseln der Birne die Methode "switchOn()" durch die Zeile

$self->switchOn();
selbst auf (also ohne daß dieser Aufruf von außen gekommen wäre).

Im übrigen können Methoden auch Rückgabewerte haben, die ebenso wie in anderen Funktionen mit "return" zurückgegeben werden.


6. Virtuelle Abbildung von realen Gegenständen

Das obige Beispielskript mit der Lampe ("OOP-Beispielskript 2") zeigt, daß Objekte, indem sie Variablen und Funktion enthalten, geeignet sind, in der Realität existierende Gegenstände innerhalb eines Computerprogramms abzubilden. Dies macht einen wesentlichen Reiz der Objektorientierten Programmierung aus. So werden in dem Beispiel bestimmte Eigenschaften einer Lampe, also ihr Zustand (eingeschaltet/ausgeschaltet), die Wattzahl der in ihr befindlichen Glühlampe und das Wechseln der Glühlampe virtuell abgebildet.

Im Grunde enthalten die obigen Abschnitte bereits die wesentlichen Grundlagen von Perls OOP. Der Rest des Textes bezieht sich mehr oder weniger auf Einzelfragen.


7. Verarbeitung von Attributen

Wie oben bereits gesagt, können Methoden auf ein Attribut mit der Syntax

$self->{attribut}

zugreifen, also z.B. den Wert auslesen oder das Attribut verändern.
Es ist auch in einer Methode möglich, ein Attribut erstmals zu definieren, das muß also nicht zwingend in "new()" geschehen.

Auch in der Methode "new()" kann man die Attribute etwa so definieren:

...
    sub new {
        my $classname = shift;
        my $self = {one  => 1};
        $self->{two} = 2;
        $self->{three} = 3;
        return bless($self, $classname);
    }
...

8. Hashsyntax bei der Argumentübergabe im Rahmen der Objektinstantiierung

Es ist recht beliebt, daß man bei der Objektinstantiierung der Methode "new()" Wertepaare in der Hashsyntax ("Schlüssel => Wert") übergeben kann, und daß in der Klasse Standardwerte definiert sind, falls kein Wertepaar übergeben wird. Dies erreicht man, indem die "Klassenmagie" in der Methode "new()" wie in dem folgenden Beispiel erweitert wird:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-Beispielskript 3

package Lamp { 
 
    sub new { 
        my $classname = shift;
        my $args = {@_}; 
        my $self = { wattzahl => $args->{wattzahl} || 40,
                     name     => $args->{name} || "Standardlampe" }; 
        return bless($self, $classname); 
    }           

    sub zeigeWattzahl {
        my $self = shift;
        print "'" . $self->{name} . "' hat eine Birne mit ";
        print $self->{wattzahl} . " Watt.\n";
    }
}

my $lamp = Lamp->new(wattzahl => 11);
$lamp->zeigeWattzahl();

Anmerkung: Wenn man diese Konstruktion verwendet, muß man darauf achten, daß der Ausdruck

dies || das

zu "das" ausgewertet wird, wenn "dies" gleich Null ist. Wenn man der Methode "->new()" also als wattzahl 0 übergibt, wird die Wattzahl in der Methode auf den Standardwert gesetzt (hier also auf 40). Das ist möglicherweise nicht das, was man gewollt hatte, und man muß selbst damit umgehen, falls das Problem in dem eigenen Programm von Bedeutung ist. Beispiel:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-Beispielskript 3 (mit Warnung)

package Lamp { 
 
    sub new { 
        my $classname = shift;
        my $args = {@_}; 
        if ($args->{wattzahl} == 0) {
            print "\nAchtung: Sollte die Wattzahl 0 sein?\n\n";
        }
        my $self = { wattzahl => $args->{wattzahl} || 40,
                     name     => $args->{name} || "Standardlampe" }; 
        return bless($self, $classname); 
    }           

    sub zeigeWattzahl {
        my $self = shift;
        print "'" . $self->{name} . "' hat eine Birne mit ";
        print $self->{wattzahl} . " Watt.\n";
    }
}

my $lamp = Lamp->new(wattzahl => 0);
$lamp->zeigeWattzahl();


9. Was "$self->{attribute}" eigentlich bedeutet

Attribute werden in etwas gespeichert, das in dieser Zeile in der Methode "new()" definiert wird:

my $self = {};

Aber was ist das eigentlich? In Perl kann man die Daten eines Arrays oder Hashes nicht nur in den herkömmlichen, benannten Arrays oder Hashes speichern, also so:

my @a = (10, 22);
my %h = (a => 10, b => 20);

Sondern man kann auch etwas ganz Ähnliches erreichen, indem man die Daten in einem anonymen Array oder Hash speichert, und dann eine Referenz darauf richtet:

my $aref = [10, 22];
my $href = {a => 10, b => 20};

Wie man sieht, ist ein anonymes Array durch eckige Klammern [...] gekennzeichnet.
Und ein anonymer Hash durch geschweifte Klammern {...}.

Auf die Daten kann man dann folgendermaßen zugreifen:

#!/usr/bin/perl

use warnings;
use strict;

# Ordinary array:
my @a = (10, 20);
print $a[1] . "\n";

# Anonymous array with array reference pointing to it:
my $aref = [10, 20];
print $aref->[1] . "\n";

# Ordinary hash:
my %h = (a => 10, b => 20);
print $h{a} . "\n";

# Anonymous hash with hash reference pointing to it:
my $href = {a => 10, b => 20};
print $href->{a} . "\n";

# By the way: Number of elements minus 1:
print $#a . "\n";
print $#{$aref} . "\n";

Das "$self" in Klassen ist also genau das: Eine Hash-Referenz, die auf einen anonymen Hash gerichtet ist.
Und auf die Daten in diesem anonymen Hashs kann mit Hilfe der Referenz zugegriffen werden.


10. Arrays und Hashes als Attribute in einer Klasse

So wie in einfachen Hashes können auch in anonymen Hashes nicht nur Skalarvariablen, sondern auch andere anonyme Arrays oder Hashes gespeichert werden. Das ist der Grund, warum man diese Datentypen auch als Attribute in einer Klasse verwenden kann.

Hier dazu wiederum ein Beispielskript:

#!/usr/bin/perl

# OOP-Beispielskript 4

use warnings;
use strict;

package Bag {

    sub new {
        my $classname = shift;
        my $self = {};
        $self->{fruits} = ["Apple", "Banana"];
        $self->{books} = {crime => "Christie",
                          cooking => "Oliver"};
        return bless($self, $classname);
    }

    sub showFruits {
        my $self = shift;
        my @fruits = @{$self->{fruits}};
        my $i;
        foreach $i (@fruits) {
            print "$i\n";
        }
        print "\n";
    }

    sub showBooks {
        my $self = shift;
        print $self->{books}->{crime} . "\n";
        print $self->{books}->{cooking} . "\n\n";
        # Or:
        my %books = %{$self->{books}};
        my $i;
        foreach $i (keys(%books)) {
            print "$i\t" . $books{$i} . "\n";
        }
    }

    sub defineThirdFruit {
        my ($self, $thirdfruit) = @_;
        $self->{fruits}[2] = $thirdfruit;
        print $self->{fruits}[2] . "\n";
    }

    sub addBook {
        my ($self, $category, $author) = @_;
        $self->{books}{$category} = $author;
        print $category . "\t" . $self->{books}{$category} . "\n";
    }
}

my $bag = Bag->new();
$bag->defineThirdFruit("Peach"); 
$bag->addBook("psychology", "Freud");
print "\n";
$bag->showFruits();
$bag->showBooks();

---

Wie in den Methoden "defineThirdFruit()" und "addBook()" gezeigt, kann auf die Elemente von Arrays und Hashes, die Attribute in einer Klasse sind, mit Hilfe der folgenden Syntax zugegriffen werden:

print $self->{fruits}[1] . "\n";

print $self->{books}{crime} . "\n";

Genaugenommen sind dies Abkürzungen für "$self->{fruits}->[1]", bzw. für "$self->{books}->{crime}".
Diese Ausdrücke bedeuten Folgendes: Wie bereits dargestellt, ist "$self" eine Referenz auf einen anonymen Hash. Dieser Hash hat einen Schlüssel (key) namens "fruits", dem als Wert (value) ein anonymes Array gegenübersteht. "$self->{fruits}" is also wiederum eine Referenz: Eine Referenz auf das anonyme Array in dem anonymen Hash.
Würde diese Reference statt "$self->{fruits}" wie in dem vorigen Abschnit "$aref" heißen, dann würde man denselben Ausdruck wie oben verwenden, um auf ein Element des anonymen Arrays zuzugreifen:

$aref->[1]

Da die Referenz aber "$self->{fruits}" heißt, lautet der Ausdruck, um auf ein Element des anonymen Arrays zuzugreifen:

$self->{fruits}->[1]

Dasselbe gilt für Hashes: Wenn die Hash-Referenz "$href" hieße, hätte man "$href->{crime}", da sie aber "$self->{books}",heißt, lautet der Ausdruck "$self->{books}->{crime}".


Will man eine foreach-Schleife verwenden, kann man nicht mit Referenzen arbeiten, da die foreach-Schleife nur über gewöhnliche Arrays läuft.

Deshalb muß die Array-Referenz "$self->{fruits}" erst dereferenziert werden, damit man in der Schleife auf die einzelnen Array-Elemente zugreifen kann. Wie dies geschieht, wird in der Methode "showFruits()" gezeigt:
Um die Referenz wird der Ausdruck "@{...}" gelegt, und das Ergebnis dieses Ausdrucks wird einer Array-Variablen zugewiesen (hier der Array-Variablen "@fruits"). Der gesamte Vorgang des Dereferenzierens sieht dann so aus:

my @fruits = @{$self->{fruits}};

Danach kann man "@fruits" als normale Array-Variable verwenden, z.B. in einer foreach-Schleife.

Auf ähnliche Weise kann die Hash-Referenz "$self->{books}" dereferenziert werden, so daß in einer Schleife auf die einzelnen Elemente des Hashs zugegriffen werden kann.
Wie in der Methode "showBooks() gezeigt, wird eine Hash-Referenz dereferenziert, indem der Ausdruck "%{...}" um die Referenz gelegt wird, und das Ergebnis einer Hash-Variablen zugewiesen wird (here "%books"). Das Dereferenzieren sieht dann insgesamt so aus:

my %books = %{$self->{books}};

"%books" ist dann ein gewöhnlicher Hash.


11. Übergabe von Objekten an Funktionen

Man kann Objekte problemlos an Funktionen übergeben.

Das ist ein großer Vorteil gegenüber anderen Datenstrukturen. Man übergibt einfach die mit dem Namen des Objekts bezeichnete Variable an die Funktion und sofort stehen einem dort das Objekt mit all seinen Attributen und Methoden zur Verfügung. Und zwar ohne, daß man noch etwas dereferenzieren müßte.
Ohne Objekte müßte man bei komplexeren Daten Referenzen auf größere "Array of Array" oder "Hash of Hash"-Strukturen übergeben. Im Vergleich dazu ist die Übergabe eines Objekts wesentlich einfacher und übersichtlicher. Hier ein Beispiel:

#!/usr/bin/perl

# OOP-Beispielskript 5

use warnings;
use strict;

package Lamp {

    sub new {
        my $classname = shift;
        my $self = {state => "off"};
        return bless($self, $classname);
    }

    sub showState {
        my $self = shift;
        print $self->{state}. "\n";
    }
}

# Funktion außerhalb der Klasse im Namensraum "main":

sub showLampState {
    my $l = shift;
    $l->showState();
}

my $lamp = Lamp->new();
showLampState($lamp);

---

In gleicher Weise kann man Objekte auch den Methoden anderer Objekte als Argumente übergeben.


12. Vererbung

Angenommen, man will eine Klasse schreiben, die einer anderen Klasse, die man bereits geschrieben hat, sehr ähnlich ist. Die meisten Attribute und Methoden sind identisch, die neue Klasse unterscheidet sich z.B. nur in zwei oder drei Methoden. Dann wäre es doch praktisch, wenn die neue Klasse die restlichen Methoden und Attribute von der bereits bestehenden Klasse übernehmen könnte, und man in der neuen Klasse also nur die Unterschiede zwischen den beiden Klassen neu schreiben müßte. Dies erreicht man in der OOP durch sogenannte "Vererbung". Hier ein Beispiel:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-Beispielskript 6

package Animal {

    sub new {
        my $classname = shift;
        my $self  = {"name" => shift};
        return bless($self, $classname);
    }

    sub startMoving {
        my $self = shift;
        print $self->{name} . " is walking.\n";
    }

    sub makeNoise {
        my $self = shift;
        print "Wuff! Wuff!\n";
    }
}

package Cat {

    use base 'Animal';

    sub showName {
        my $self = shift;
        print $self->{'name'} . "\n";
    }

    sub makeNoise {
        my $self = shift;
        print "Meow!\n";
    }
}

my $cat = Cat->new("Tibby");
$cat->showName();
$cat->startMoving();
$cat->makeNoise();

---

Durch das "use base ..." übernimmt die untergeordnete Klasse ("Cat") von der Elternklasse ("Animal") alle Attribute und Methoden, hier das Attribut "name" und die Methoden "startMoving()" und "makeNoise()".
Bei der Objektinstantiierung geht das Argument "Tibby" durch das "use base ..." offenbar an die "new()"-Methode von "Animal".
Der Aufruf "$cat->showName();" zeigt, daß das "Cat"-Objekt das Attribut "name" enthält.
Die Methode "makeNoise()" wurde zwar von "Animal" übernommen, wird aber in "Cat" durch einen anderen Inhalt überschrieben. Der Aufruf "$cat->makeNoise();" bezieht sich somit auf die überschriebene Methode im "Cat"-Objekt.

Neben "use base ..." gibt es noch "use parent ...". Ferner gibt es noch eine ältere Konstruktion der Vererbung mit Hilfe des "@ISA"-Arrays. Falls einem die obige Syntax nicht genügt, müßte man hierzu also noch gesondert lesen. Ich habe Vererbung bisher nur vereinzelt benutzt.


13. OOP-Syntax in Perl/Tk

Wenn man sich mit den obigen Grundsätze von Perls traditioneller OOP vertraut gemacht hat, versteht man auch besser die Syntax, die in OOP geschriebene Perl-Module verwenden. Dies gilt insbesondere für die Befehle, die man in Perl/Tk zur Erstellung grafischer Oberflächen verwendet.
Ein typisches, minimales Perl/Tk-Skript (das ein einfaches Fenster mit einer winzigen Aufschrift "Hallo" erstellt) sähe z.B. so aus:

#!/usr/bin/perl

use warnings;
use strict;

use Tk; 

my $mw = MainWindow->new();
my $lab = $mw->Label(-text => "Hello");
$lab->pack();
$mw->MainLoop();

---

Die Perl/Tk-Befehle enthalten typische OOP-Syntax. Man könnte sie ohne Tk mit eigenen Klassen gewissermaßen "abfangen", und so sozusagen Tk ohne dessen Funktionalität simulieren:

#!/usr/bin/perl

use warnings;
use strict;

# OOP-Beispielskript 7

package MainWindow {

    sub new {
        my $classname = shift;
        my $self = {};
        print "New MainWindow would have been created.\n";
        return bless($self, $classname);
    }

    sub Label {
        my $self = shift;
        return Label->new(@_);
    }

    sub MainLoop {
        my $self = shift;
        print "Now the mainloop would run.\n";
    }
}

package Label {

    sub new {
        my $classname = shift;
        my $args = {@_}; 
        my $self = { -text => $args->{-text} || "" };
        print "New label-widget would have been created. ";
        print "Its text would be '" . $self->{-text} . "'.\n";
        return bless($self, $classname);
    }

    sub pack {
        my $self = shift;
        print "The label-widget would have been packed now.\n";
    }
}

my $mw = MainWindow->new();
my $lab = $mw->Label(-text => "Hello");
$lab->pack();
$mw->MainLoop();

---

Wenn man also Perls OOP versteht, begreift man auch Perl/Tk (und andere objektorientierte Module) besser.


14. Interaktion von Objekten und Klassendesign

Mit OOP ist es auch in Perl möglich, umfangreiche Programme zu schreiben, die nicht unübersichtlich werden müssen.
Typischerweise besteht ein solches Programm aus mehreren Klassen, während im Hauptbefehlsbereich des Skripts nur ein oder zwei Befehle stehen: Eine Objektinstantiierung und gegebenenfalls der Aufruf einer weiteren Methode, die das Programm in Gang setzt.
Die weiteren Objekte werden dann innerhalb der aufgerufenen Klasse instantiiert. Man spricht von einer "has a"-Beziehung zu diesen Objekten.
Die eigentliche Kunst der OOP besteht darin, in sinnvoller Weise festzulegen, was die Objekte abbilden sollen und in welchem Verhältnis die Objekte zueinander stehen sollen. Man spricht auch vom "Modellieren" von Klassen, fast wie bei den Skulpturen eines Bildhauers.

Klassen sollten in sich abgeschlossen sein. Von außen sollten nur Methoden aufgerufen, aber nicht auf die internen Attribute einer Klasse zugegriffen werden. Man kann sich Objekte als Maschinen in Software vorstellen, die von anderen Objekten "benutzt" werden. Bei einem mechanischen Getränkedosenautomaten in der realen Welt z.B. wirft eine Person eine Münze ein und erhält daraufhin eine Getränkedose. Mit der internen Mechanik des Automaten kommt der Benutzer nicht in Berührung. Er weiß von ihr nichts, braucht von ihr aber auch nichts zu wissen (ohne daß das schaden würde).
In einem Programm kann ein Objekt ebenso einfach benutzt werden, etwa mit einem Aufruf wie:

my $getraenk = $getraenkeautomat->gibMirEineGetraenkedose(-centmuenze => 50);

oder:

$T_800->beschuetzeMichVorDemT_1000(-waffe => "Pumpgun");

Genau so sollten Objekte gestaltet sein.

Objekte sollten in sich abgeschlossen sein und auch von anderen Objekten nur soviel "wissen", wie sie benötigen. Das ist nicht immer leicht durchzuhalten. Leicht kommt man in Versuchung, die Beziehungen zwischen mehreren Objekten über ein übergeordnetes, "allwissendes" Objekt zu regeln. Ein solches "Gottobjekt", auch "big hairy object" genannt, sollte man möglichst vermeiden.

Bei großen Programmen mit grafischer Oberfläche ist es sinnvoll, die Datenverarbeitung von der grafischen Darstellung der Daten in mehreren Klassen zu trennen. Dies ist Gegenstand des Konzepts "Model View Controller (MVC)".
Im Idealfall sollte es damit möglich sein, die grafische Anbindung (wie z.B. Tk, Gtk, Qt) durch Austausch der Klasse "View" zu wechseln. In der Praxis beschränkt man sich aber meist doch auf die Unterstützung nur eines dieser Toolkits.


15. Ein längeres Programmbeispiel

Zum Abschluß noch ein längeres Programmbeispiel. Es stellt mehrere sich bewegende "Ball"-Objekte grafisch in einer Tk-Oberfläche dar:

In Bezug auf die objektorientierte Programmierung wurden ausschließlich Konstruktionen verwendet, die in dem obigen Text erläutert wurden.
Da das längere Programm auf dieser Seite unübersichtlich wäre, habe ich es ausgelagert. Man findet es hier.



Email: hlubenow2 {at-symbol} gmx.net
Zurück