Blüten in der objektorientierten Programmierung

OOP zum Selbstzweck ist der Praxis ferner denn je, ein Beispiel in Perl

Instanzen machen nur dann einen Sinn, wenn deren Methoden genutzt werden und es einen Grund gibt, diese Methoden in einem eigenen Namensraum bzw. einer eigenen Klasse zu definieren.

In der Regel freuen sich Programmierer darüber, dass sie nicht alles selbst entwickeln müssen, sondern:

So ist das auch mit Perl-Modulen, die dazu gemacht sind, HTTP-Requests abzusetzen und Responses auszuwerten. Hierfür wurde vor einigen Jahren die libwww entwickelt, eine Library für den Zugang zum WWW.

Doch ach, so einfach ist das gar nicht, denn es braucht mehrere Klassen und Instanzen. Als Erstes wird eine Instanz der Klasse UserAgent (UA) erstellt. Logisch, denn einer muss den Request ja feuern. Nun, die UA-Instanz allein kann es nicht, wir müssen noch ein Request-Objekt mitgeben. Und da ein Request bestimmte Request-Header haben sollte, brauchen wir noch ein Header-Objekt. Derer nicht genug, benötigen wir für etwaige Cookies wiederum ein extra Objekt für Cookies.

Je nach Vorgehensweise benötigen wir ggf. noch ein weiteres Objekt für die zu sendenten Parameter oder beliebigen Content (POST, PUT). Mit diesen vier bzw. fünf Objekten können wir nun endlich einen Request an einen bestimmten URL absetzen. Doch, was kriegen wir zurück? Eine Response etwa? Weit gefehlt! Denn wir bekommen ein Response-Objekt. Und mit diesem Objekt dürfen wir dann bestimmte Methoden aufrufen, um an die Response-Header und den Message-Body ranzukommen.

Ohje, gehts noch!?

Zweckdienliche Objektorientierte Programmierung

Natürlich lässt sich die oben beschriebene Aufgabenstellung auch anders lösen. Es stellt sich die Frage, warum eine Handvoll Objekte erstellt werden muss und die nächste Frage ist die nach dem Zusammenwirken von Instanzen verschiedener Klassen. Ganz sicher haben wir mal was von Vererbung gehört oder gelesen, aber Derartiges ist in der oben geschilderten Vorgehensweise nicht einmal ansatzweise zu erkennen. Ebensowenig ist ersichtlich, ob einzelne Methoden delegiert wurden.

Im Grunde genommen brauchen wir überhaupt keine Objekte. Request-Header lassen sich sehr schön in einem Hash abbilden, ebenso auch Cookies. Parameter oder anderweitig zu sendente Inhalte sind einfach nur Strings oder Byte-Sequenzen. Es macht dann auch nicht mehr viel Sinn, den Request an sich als ein Objekt zu formulieren, denn Request-Method, URL und ob HTTP/HTPS, Version 1.0 oder 1.1 sind ebenfalls nur Literale.

So ist denn auch die Response nur noch eine Bytesequenz, von einzelnen Header-Zeilen durch eine Leerzeile getrennt. Das wären bestenfalls, sofern für alle Header ein Hash verwendet wird, zwei Scalare, wofür es mit Sicherheit keine Instanz irgendeiner Klasse braucht zur Weiterverarbeitung. Auf herkömmliche Art und Weise, prozedural gelöst, sähe es beispielsweise so aus:

    my ($header, $body)   = request( url => 'http://example.org/index.html', method => 'GET', usw. );

Wobei die Argumente der request()-Funktion sowohl Cookies, als auch weitere Header darstellen können, ebenso einen etwaigen Message-Body, welcher mit POST oder PUT gesendet werden soll. Bleibt die Frage, ob wir überhaupt Objekte brauchen für solche oder ähnlich triviale Aufgaben. Mit Sicherheit brauchen wir kein Response-Objekt um einen String zu zerlegen.

Es lohnt sich jedoch, darüber nachzudenken, für den UserAgent (UA) ein Objekt zu erstellen. Das lohnt sich deshalb, weil ein UA, mit bestimmten Parametern konfiguriert, in der Lage sein sollte, mehrere Requests zu feuern, beispielsweise mit dem Request-Header Connection: Keep-Alive. Aber auch in diesem Fall ist die Response nur eine Bytesequenz mit mehreren Antwortseiten.

Objekte als Datenträger

Das Objekt als Instanz einer Klasse wird einmal erstellt (Konstruktor) mit bestimmten Parametern. Danach wird im Verlauf des Programms mit dem Objekt mehrfach operiert. Und das ist der Vorteil dieser Art von OOP: Die Parameterdaten zur Erstellung des Objekts müssen nur einmal eingegeben werden.

Klassen zu Trennen von Namensräumen

Untenstehendes Beispiel macht es deutlich: Gleichnamige Methoden können nebeneinander verwendet werden:

    HTTP->request();
    HTTPs->request();

Aggregation und Methoden delegieren

Mehrere Instanzen verschiedener Klassen werden in einem einzigen Objekt zusammengefügt. Die Methoden dieser Subklassen werden an dieses Hauptobjekt delegiert. Untenstehendes Beispiel bezieht sich auf die libwww (LWP), deren Anwendung damit erheblich vereinfacht wird:

package myLWP; # wrapper für HTTP::Request, LWP::UserAgent usw. use strict; use warnings; use LWP::UserAgent; use HTTP::Request; sub new{ my $class = shift; my %param = ( uri => '', method => 'GET', enctype => 'application/x-www-form-urlencoded', body => '', headers => {}, @_); %{$param{headers}} = (%{$param{headers}}, ("Content-Type" => $param{enctype})); return eval{ my $self = bless{ ua => LWP::UserAgent->new(), req => HTTP::Request->new( $param{method} => $param{uri}, ) }, $class; $self->{req}->header( %{$param{headers}} ); $self->{req}->content($param{body}); $self; } } # Quick Request, liefert die komplette Response sub quickreq{ my $class = shift; my $self = $class->new(@_); return $self->request->response; } # Methoden für Request-Objekt # ua sub request{ my $self = shift; $self->{res} = $self->{ua}->request($self->{req}); return $self; } # Methoden für Response-Objekt sub response_status{ my $self = shift; my ($code, $lit) = split /\s+/, $self->{res}->status_line; return $code; } # Gesamte Response mit Header sub response{ my $self = shift; return $self->{res}->as_string; } sub response_body{ my $self = shift; return $self->{res}->content; } # Alle header der Response sub response_header{ my $self = shift; my $name = shift; if( $name ){ return $self->{res}->header($name); } else{ my ($hs, $body) = split(/\n\n/, $self->{res}->as_string, 2); return $hs; } } 1;#########################################################################

Alternative zu Objekt in Objekt ist die Factory

Abstrakt gesehen lässt sich so ziemlich jede Programmieraufgabe auf den Aufruf einer Funktion oder Methode beschränken bzw. abbilden, denn eine Klasse allein bewegt ja noch nichts. Da wäre zunächst die Delegation von Methoden, wenn Instanzen nicht verwandter Klassen gebraucht werden. Beispiel:

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

    # Delegiere die gleichnamige Methode
    sub param{
        my $self = shift;
        return $self->{CGI}->param(@_);
    }

    # das wird damit möglich
    $main->param('foo');

Diese paar Zeilen vermitteln allein schon die Idee, eine Instanz der Klasse 'main' zu haben als ein Objekt, welches sich verantwortlich zeichnet für alle im Programm zu lösenden Aufgaben. Programmweite oder gar globale Variablen können so vermieden werden, denn die main-Instanz trägt alle Variablen als Referenzen bzw. Attribute mit sich rum. Für den Entwickler hat das den entscheidenden Vorteil, dass er an einer beliebigen Stelle im Programm mit einem Aufruf des Data::Dumper den Status aller Variablen abfragen kann ohne mühseelige Kleinarbeit.

Alles wird überschaubarer, auch die Anzahl der an Methoden zu übergebenden Argumente. Des Weiteren können Methoden infolge AUTOLOAD ausgelagert werden. Eine solche Methode ist dann eine Datei, in welcher bei Bedarf weitere Module oder Klassen eingebunden werden können. Mehr noch: In der eigenen main-Class sind die Namen nicht verwandter Klassen völlig uninteressant und deren Instanzen tauchen in der eigenen Klasse überhaupt nicht auf, Beispiel:

    my $cfg = $main->configini('config.ini') or die $@;

Wobei: Die Methode configini() bindet das Modul Config::Tiny ein und könnte genausogut anstelle dessen das Modul Config::IniFiles verwenden, ohne dass sich am Aufruf der Methode in der main-Class was ändert.


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.