Moose: Eine (post-)moderne OOP-Erweiterung für Perl

Seite 2: Nachteile und Alternativen

Inhaltsverzeichnis

Es gibt jedoch auch gute Gründe auf Moose zu verzichten. Ein bereits vorhandener Codeberg ist kein solcher Grund. Moose-Klassen lassen sich vollständig wie Klassen der alten Schule benutzen, inklusive Vererbung und aller UNIVERSAL-Methoden wie can und isa. Nur sollte man nicht vergessen, dass Moose-Akzessoren Methoden und keine Hash-Schlüssel sind, weshalb sie nun auch mit vererbt werden.

Für den umgekehrten Fall (Moose-Klasse erbt von einer Klasse im alten Stil) gibt es MooseX::NonMoose. Überhaupt ist das vielfältige und kreative Ökosystem an Erweiterungen im MooseX::*-Namensraum ein Argument für Moose. Das X am Wortende ist eine CPAN-Konvention für Module, die ohne Absprache mit dem Besitzer des Namensraumes (hier Moose) erstellt wurden.

Zum echten Showstopper kann der erhöhte Rechenaufwand zur Startzeit werden. Da dieser zum großen Teil von der Komplexität des Metaobjektprotokolls hervorgerufen wird, hat eine Gruppe vorwiegend asiatischer Programmierer mit Mouse ein fast kompatibles Moose ohne Class::MOP und die meisten anderen Abhängigkeiten geschaffen, weshalb man Mouse auch gerne als "Moose ohne Geweih" bezeichnet. Wer Moose::Any statt Moose verwendet, kann mit einer kleinen Änderung zwischen Moose und Mouse wechseln und von Fall zu Fall anhand eigener Benchmarks entscheiden.

Aber wenn selbst Mouse zu langsam ist oder weit weniger Funktionalität ausreicht, empfiehlt sich ein Griff zu Moo (minimalistische OO), das nur den Teil des "täglichen Bedarfs" von Moose mit einer leicht abweichenden Syntax implementiert.

Wesentlich länger gibt es die altehrwürdige und vielbenutzte Class::Accessor-Familie, mit der der Entwickler lediglich automatisch erzeugte Getter- und Setter-Methoden erhält. Die ungeschlagen schnellste und minimale Lösung ohne jegliche Abhängigkeiten stellt hier Object::Tiny dar.

Der Vorteil der autogenerierten Accessoren (Getter- und Setter-Methoden) war der Hauptentstehungsgrund der meisten OOP-Module, denn in nacktem Perl 5 braucht es mehrere kürzbare Zeilen, um Attribute zu erstellen. An private war ohne noch mehr "Boilerplate-Code" gar nicht zu denken.

Einem der neuen CPAN-Mottos folgend ("Hört auf die Räder neu zu erfinden — baut Raketen") würde eine kleine Klasse Rakete, die lediglich das Attribut Geschwindigkeit, sowie eine Methode zum Anhalten hat, wie folgt aussehen:

use strict;
use warnings;

package Rakete;

sub new {
my ($klasse, %parameter) = @_;
my $objekt = bless ({'v' => $parameter{geschwindigkeit} }, $klasse);
...
return $objekt;
}

sub geschwindigkeit {
my ($objekt, $v) = @_;
$objekt->{'v'} = $v if defined $v;
return $objekt->{'v'};
}

sub stop {
my ($objekt) = shift;
$objekt->{'v'} = 0;
}

Das Gleiche mit Moose:

package UFO;
use Moose;

has 'geschwindigkeit' => (is => 'rw');

sub stop {
my ($objekt) = shift;
$objekt->geschwindigkeit(0);
}

Die Ersparnis ist offensichtlich und geht weit über das Schreiben der Pragmas, des Konstruktors und der Accessoren hinaus. Oft genug täuschen Vergleiche minimaler Beispiele, da der Gewinn nur einen kleinen Teil der späteren Klassengröße ausmacht. Doch je mehr Funktionalität von Moose man verwendet, desto drastischer schrumpfen die Quelldateien.

Das 'rw' steht wie bei Datenträgerrohlingen auch für "read and write", 'ro' dementsprechend für "readonly" — der Wert solcher Attribute ließe sich nur einmalig während der Initialisierung setzen.

Sämtliche hier gezeigten Beispiele machen reichlich Gebrauch vom fetten Pfeil (=>). Das hat zwar auch visuelle Vorteile, erspart aber vor allem, das Vorangehende in Anführungszeichen zu setzen. Um sich Erklärungen zu sparen, gehört es mancherorts zum guten Ton, genau das aber trotzdem zu tun. Benutzt werden beide Klassen identisch.

   my $objekt = Klasse->new(geschwindigkeit => 20);
$objekt->stop;
say $objekt->geschwindigkeit; # gibt 0 aus
$objekt->geschwindigkeit(30);
say $objekt->geschwindigkeit; # 30

Da meist benannte Paramter in einer Hashreferenz übergeben wurden, die der Konstruktor mit my ($klasse, $parameter) = @_; aufnimmt, sieht man in altem Code jedoch häufig:

   my $arv = Rakete->new({geschwindigkeit => 20});

Moose reagiert auf beide Schreibweisen gleich.

Um die Syntax noch mehr nach bekannter OOP aussehen zu lassen, steht Entwicklern die Erweiterung MooseX::Declare zur Verfügung. Damit heisst eine Methode method und eine Klasse class, was darüber hinaus das use Moose; spart, das sonst nach jeder Package-Deklaration zu stehen hat, um aus dem Namensraum eine Moose-Klasse werden zu lassen. Manchmal fügt man auch no Moose; ein, um Befehle wie has zu deaktivieren.

   use MooseX::Declare;
class UFO {

has 'geschwindigkeit' => (is => 'rw');

method stop {
my ($objekt) = shift;
$objekt->geschwindigkeit(0);
}
}

# alternativ für kleine Methoden:
method stop { shift->geschwindigkeit(0) }

MooseX::Declare verlangt die Klasse in geschweifte Klammern zu legen und führt im Hintergrund noch ein paar andere Dinge aus. Es verhindert versehentliche Verschmutzung von Namensräumen mit namespace::autoclean und ruft das notorische

   __PACKAGE__->meta->make_immutable;

auf, das ansonsten am Ende der meisten Moose-Klassen steht. Mit dieser Zeile verzichtet die Klasse auf ihr Recht, sich zur Laufzeit zu verändern und gewinnt dafür an Geschwindigkeit. Ist das nicht gewünscht, sollte bei Verwendung von MooseX::Declare dem Klassennamen ein is mutable folgen.