Corinna: Ein modernes und reifes Objektsystem für Perl 5

Seite 2: Methodisch gesehen

Inhaltsverzeichnis

Corinna-Methoden beginnen mit method, gefolgt von einem Namen, der dem Namen einer Subroutine gleichen darf, denn für beides gibt es getrennte Namensräume. Da use feature 'class'; immer use feature 'signatures'; beinhaltet, besitzen Corinna-Methoden nach den optionalen Attributen immer Signaturen – mindestens leere, runde Klammern. Diese kennen nur positionale Argumente, die dann optional sind, wenn ihnen ein Default-Wert zugewiesen ist. Wer sich daran erinnert, dass Konstruktoren sehr wohl benannte Argumente verstanden, dem sei gesagt, dass der Konstruktor im OO-System Corinna automatisch generiert wird und keine vom Nutzer geschriebene Methode ist. Um dennoch mit den Konstruktor-Argumenten Berechnungen anzustellen, bevor das Objekt erzeugt wird, können Entwicklerinnen und Entwickler den Phaser ADJUST{...} verwenden. Phaser sind Codeblöcke, die zu bestimmten Zeiten oder Bedingungen ausgeführt werden (wie etwa BEGIN{...} oder LAST{...}).

Innerhalb von ADJUST{...} lassen sich alle Feldvariablen auslesen und verändern. Später soll noch DESTRUCT{...} hinzukommen, mit dem Programmierer noch aufräumen, bevor das Objekt abgebaut wird.

Im Gegensatz zu Perl-5.0-Objekten, darf man $self nicht in der Signatur angeben. Diese Spezialvariable beinhaltet die aktuelle Instanz, und mit ihr lassen sich alle privaten und öffentlichen Methoden aufrufen. Sie ist automatisch in jeder Methode bekannt, solange es keine Klassenmethode ist. In letzteren kennt man $class, die auch nur Klassenmethoden rufen kann.

Methoden sind von Haus aus öffentlich. Mit dem Attribut :private dagegen nur für die aktuelle Klasse oder Rolle sichtbar, genau wie Feldvariablen. Und wie bei letzteren entstehen Klassenmethoden mit dem Zusatz :common, innerhalb derer keine Instanzmethoden mehr zugänglich sind und auch keine gewöhnlichen Feldvariablen. Methoden mit :common, aber ohne Signatur und Block sind abstrakt, was im Folgenden näher erläutert wird. Das Attribut :overrides unterdrückt lediglich die Warnung, die anzeigt, dass hier eine Methode überschrieben wurde. Da Klassen- und Instanzmethoden eigene Namensräume besitzen, lässt sich nur innerhalb einer Methodenart überschreiben.

Es sind auch noch sogenannte Methoden-Modifikatoren wie :before, :after und :around angedacht. Mit ihnen entstehen Codeblöcke, die sich vor, nach oder vor und nach der gleichnamigen Methode ausführen lassen. Diese Funktionalität ist aber eher von experimenteller Natur und angesichts der Tatsache, dass bisher kein Methoden-Attribut implementiert ist, erscheint ihre Zukunft eher ungewiss.

Rollen beginnen mit role und sind Namensräume für Feldvariablen und Methoden. Von einer Rolle (manchmal Trait genannt) lässt sich keine Instanz bilden, aber Klassen und andere Rollen können sie konsumieren. Durch diesen Vorgang wird die öffentliche Schnittstelle der Rolle zum Teil der privaten API des Konsumenten. Der besitzt jedoch keinen Zugriff auf die private API der Rolle, deswegen können auch keine Namenskonflikte zwischen Feldvariablen einer Rolle und der konsumierenden Klasse entstehen. Namenskonflikte zwischen Methoden der Rolle und ihres Konsumenten sind Fehler, welche bereits zur Kompilierungszeit zum Abbruch führen.

Der ADJUST{...}-Phaser einer Rolle wird nach dem ADJUST des Konsumenten ausgeführt und DESTRUCT{...} vor dem Konsumenten.

Rollen besitzen zwei wesentliche Anwendungsfälle, die sich bei Corinna kombinieren lassen. Zum einen, um Hilfsmethoden bereitzustellen, die nirgendwo elegant in die Vererbungshierarchie passen. Zum anderen müssen Methoden einer Rolle, die keine Signatur oder Code besitzen, also abstrakt sind, vom Konsumenten überschrieben werden. Das entspricht der Funktionalität einer Schnittstelle (interface).

Ein Beispiel:

role Counter {

field $counter :reader = 0;

method increment () { $counter++ }
method display_counter;
}

class Rocket 1.0 :does(Counter) {

method display_counter () {
# eigentliche implementation
}
}

class eröffnet eine Klasse und lässt sich wie package als Anweisung schreiben oder einem Block aus geschweiften Klammern voranstellen. Wie bei package, darf nach Schlüsselwort und Namen die optionale Versionsnummer stehen.

Das wichtigste und einzige bisher implementierte Attribut ist :isa(..), das bestimmt, von welcher Klasse geerbt wird, da die UNIVERSAL-Methode ->isa seit Perl 5.0 solche Abhängigkeiten abfragt. Darüber hinaus prüft :isa(..) auch die Versionsnummer der Elternklasse und wirft bei negativem Ergebnis einen Fehler aus:

class Example::Subclass 1.0 :isa(Example::Base 2.345) { ... }

Bisher ist nur Einfachvererbung vorhergesehen, was sich bei starker Nachfrage seitens der Perl-Programmierer noch ändern kann. Solange feature experimentell sind, dürfen sie sich ändern. Immerhin ist geplant, dass mit :does(..) beliebig viele Rollen konsumiert werden dürfen. Ist eine Klasse als :abstract markiert, darf sie abstrakte Methoden ohne Signatur und Block enthalten, lässt sich aber dafür nicht instanziieren. Abstrakte Methoden müssen wie bei Rollen überschrieben werden oder die Instanziierung scheitert an einem Fehler.

Corinna-Objekte werden wie auch Objekte alter Schule vom builtin blessed, als auch von der gleichnamigen Scalar::Util-Routine erkannt. Der aus beiden Quellen beziehbare Befehl reftype kann sie hingegen unterscheiden. Objekte der alten Art liefern hier HASH, ARRAY oder was auch immer mit bless zum Objekt erhoben wurde. Bei Corinna-Objekten antwortet reftype mit OBJECT.