Perl: Dependency Injection als Design Pattern

Einfache Entwurfsmuster sind oft schwer zu verstehen

Viele mir bekannte Praktiker, ich gehöre auch dazu, halten nicht viel von Entwurfsmustern. Praxisnah programmieren heißt zweckmäßig programmieren und den Anwender interessiert ein etwaiges zugrunde liegendes Entwurfmuster herzlich wenig. Hier jedoch gehts um ein paar interessante Sachen und Hintergründe zum klassenübergreifenden Datenaustausch aus der Sicht der Programmierer.

Dependency Injection, Delegation, Factory

Oft werden im eigenen Code Instanzen fremder Klassen (nicht mit der eigenen Klasse verwandte Klassen) benötigt. In der Praxis unterscheide ich zunächst, wann das passiert, also, ob das von Anfang an feststeht oder ob es sich erst zur Laufzeit ergibt. Darüber hinaus wäre noch zu unterscheiden, ob fremde Klassen hard coded oder per externe Konfiguration einzubinden sind. In meinem Framework gibt es alle möglichen Varianten und so stellt sich auch die Frage, inwieweit sowas wartungsfreundlich ist oder auch nicht. Lassen wir dazu einfach ein paar Beispiele sprechen.

Das Einbinden einer fremden Klasse steht zu Beginn fest

Die eigene Klasse sei die package main, erstellen wir eine Instanz der eigenen Klasse:

use CGI;

my $main = bless{
    CGI => CGI->new()
}, 'main';

# oder im Konstruktor
sub new{
    my $class = shift;
    return bless{
        CGI => CGI->new()
    }, $class;
}

# oder als Übergabe
my $cgi = CGI->new();
sub new{
    my $class = shift;
    my $cgi   = shift;
    return bless{
        CGI => $cgi
    }, $class;
}

Das Attribut CGI ist eine Instanz der nicht verwandten Klasse CGI. Anstatt alle Methoden von der Klasse CGI zu erben, delegieren wir nur eine Methode:

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

Und das ist auch der eigentliche Grund dafür, das CGI-Modul von Beginn an einzubinden: Die Methode param() wird auf jeden Fall benötigt. Die Delegation erlaubt es, die Methode param() als eine eigene Methode aufzurufen.

Instanz einer fremden Klasse zur Laufzeit einbinden

So richtig Sinn macht das nur, wenn die namentlich gewünschte Methode in eine externe (und dedizierte) Datei ausgelagert wird. Das könnte dann zum Beispiel so aussehen:

# Datei param.pm
use CGI;
*param = sub{
    my $self = shift;
    $self->{CGI} = CGI->new() unless exists $self->{CGI};
    return $self->{CGI}->param(@_);
};
__END__

Beim ersten Aufruf der Methode param() an einer beliebigen Stelle im eigenen Code wird obenstehende Datei param.pm per AUTOLOAD eingebunden, d.h., deren Code wird kompiliert. Die Instanz der Klasse CGI wird als Attribut der eigenen Klasse erstellt, dies aber auch nur beim ersten Aufruf der Methode param(). Danach kann die param()-Methode beliebig oft aufgerufen werden.

Aufgrund einer solchen Vorgehensweise wird die fremde Klasse vollständig von der eigenen Klasse abstrahiert. Das heißt dass die Instanz der eigenen Klasse den Namen der fremden Klasse überhaupt nicht kennen muss und letztendlich ist die fremde Klasse austauschbar. Diese Herangehensweise ist auch bekannt unter der Bezeichnung Factory Method.

Framework Specials

Charakteristisch für mein Framework ist die Bindung eines URL an eine Subklasse. Der Name der Subklasse ist einerseits also von außen konfigurierbar und steht andererseits auch erst dann fest, nachdem der Request entgegengenommen wurde.

Fazit

Design Patterns sind was für Theoretiker. In der Praxis gibt es fließende Übergänge, so dass es wenig sinnvoll bzw. zielführend ist, nach einem bestimmten Entwurfsmuster entwickeln zu wollen. Gründe dafür, bestimmte Dinge nicht zu tun, gibt es da schon eher. Instanzen fremder Klassen dem eigenen Konstruktor zu übergeben, führt sehr schnell zu einem unübersichtlichen Code der auch schwer zu warten ist. Besser ist es, solche Instanzen entweder im eigenen Konstruktor zu erzeugen oder im Rahmen einer Factory.


Anbieter: nmq​rstx-18­@yahoo.de, die Seite verwendet funktionsbedingt einen Session-Cookie und ist Bestandteil meines nach modernen Aspekten in Perl entwickelten Frameworks.