Fluent Interfaces und der PHP-Weg

Thursday, 06 September 2007, 11:23 von Blackflash

Bereits vor einiger Zeit bin ich auf Fluent Interfaces von Martin Fowler gestoßen. Es ist ein einfaches Prinzip mit dem man sich ein wenig Schreibarbeit ersparen kann. Nur zur Information erkläre ich das Prinzip auf die Schnelle:

class Foo { public function doSomething () { // do something } public function doAnotherThing () { // do another thing } public function doLastThing () { // do the last thing } } $foo = new Foo(); // Zum Initialisieren $foo->doSomething(); $foo->doAnotherThing(); $foo->doLastThing();

Das ist die althergebrachte Weise zum Initialisieren. Natürlich könnte man Selbiges auch im Konstruktor tun, aber das ist für dieses Beispiel irrelevant. Hier das Beispiel mit Fluent Interfaces.

class Foo { public function doSomething () { // do something return $this; } public function doAnotherThing () { // do another thing return $this; } public function doLastThing () { // do the last thing return $this; } } $foo = new Foo(); // Zum Initialisieren $foo->doSomething() ->doAnotherThing() ->doLastThing();

Es wird also lediglich die eigene Instanz zurückgegeben. Dies verhindert zwar, dass man einen anderen Wert zurückgeben kann. Aber bei vielen Operationen ist dies nicht notwendig. Zu häufig gibt es void-Rückgabewerte. Und gerade für diese Fälle sind Fluent Interfaces absolut geeignet.

Wenn man sich bereits an die Herangehensweise gewöhnt hat, will man es immer wiederverwenden. So geht es mir zumindest. Besonders beim Setzen von Variablen ist es mir letztens aufgefallen, dass ich den Drang habe, es auch bei Variablen zu nutzen.
Leider funktionieren Konstrukte wie $foo->bar = 'bar'->foo = 'foo'; leider nicht. Deshalb muss man das Setzen von Variablen wohl über eine Methode machen. Viele behaupten, dass man das Setzen und Holen von Werten eines Objekts sowieso über sogenannte getter- und setter-Methoden machen sollte, was ich per se nicht verneinen will. In den meisten Fällen ist es der richtige Weg. Man ist flexibel und kann sehr schnell etliche Algorithmen verändern, aber das soll nicht das Thema dieses Blogeintrags sein.
Im PHP-way werden auch solche getter- und setter-Methoden verwendet. Aber es gibt magische Methoden zum Überladen von Variablen, get und set sind dabei die wichtigsten. Diese sind in den meisten Fällen genauso sinnvoll zu verwenden. Anpassen kann man es auch sehr einfach. Ein paar kleinere Beispiele, wie es funktionieren könnte:
Erstmal ein Beispiel mit einigen Variablen, die komplett ohne zugehörigen Algorithmus gesetzt werden können.

class Foo { private $_content = array( 'foo' => '', 'foobar' => '', 'bar' => '' ); public function __set ($name, $content) { if(!isset($this->_content[$name])) { throw new Exception ('Variable "' . $name . '" not set'); } $this->_content[$name] = $content; } }

Jetzt ein Beispiel, das eine kleine Anzahl von Variablen setzen kann, die jedoch teilweise einen Algorithmus benötigen.

class Foo { private $_content = array( 'value' => '', 'square' => '', 'cubic' => '' ); public function __set ($name, $content) { // Überprüfung aus Gründen der Kürze entfernt switch ($name) { case 'square': $content *= $content; break; case 'cubic': $content *= $content*$content; break; } $this->_content[$name] = $content; } }

Und nun ein Beispiel, in dem viele Variablen mit unterschiedlichen Algorithmen gesetzt werden sollen. Der Ansatz basiert auf der Annahme, dass eine bestimmte Konvention für zugehörige setter-Methoden eingehalten werden. In diesem Fall ist es notwendig, dass es für jede Variable eine zugehörige Methode mit dem Präfix "set" gibt. Der danach folgende Teil ist der Name der Variable, die jedoch im Methodennamen mit einerm Großbuchstaben beginnt.

class Foo { private $_content = array( 'value' => '', 'square' => '', 'cubic' => '' ); public function __set ($name, $content) { // Überprüfung aus Gründen der Kürze entfernt $this->{'set' . ucfirst($name)}($content); } public function setValue ($value) { ... } public function setSquare ($value) { ... } public function setCubic ($value) { ... } }

Somit kann eine Vielzahl von Variablen ganz intuitiv gesetzt werden. Einfach $obj->value = '2' o.Ä. Viele bezeichnen so was als den PHP-Weg.
Leider lässt sich der PHP-Weg kaum mit Fluent Interfaces vereinbaren, da Variablenzuweisungen kein Objekt zurückgeben können. Deshalb schlage ich einfach vor, eine weitere Methode zu implementieren:

class Foo { // Rest bleibt public function set ($name, $value) { $this->$name = $value; return $this; } }

So kann man beides unter einen Hut bringen. Es ist möglich, sowohl Fluent Interfaces zu benutzen, als auch den PHP-Weg zu beschreiten. Beim Setzen einer Variable kann man den intuitiven Weg gehen und beim Setzen einer Vielzahl solcher, kann man Fluent Interfaces benutzen.

$foo = new Foo(); $foo->square = 2; $foo->set('value', 10) ->set('square', 5) ->set('cubic', 3);

Im Übrigen ist die Methode set nicht notwendig, da man direkt __set aufrufen kann, aber das ist meines Erachtens eine Zweckentfremdung, die nicht notwendig ist.
Ich hoffe, ich konnte Widersprüchlichkeit zwischen Fluent Interfaces und dem PHP-Weg ein wenig reduzieren, sodass man beide Ansätze effektiv und parallel einsetzen kann.

Kommentare


Philipp schreibt:

Hi,

ich habe mich im Zuge meiner Bachelorarbeit mit dem Thema beschäftigt.
Klar sind Fluent Interfaces kein Allheilmittel aber sie machen vor allen Dingen da Sinn wo man eine API anbieten will die sich so gut wie von allein erklärt und den Benutzer davor bewahrt sie falsch zu benutzen. Aber das Erstellen eines Fluent Interface ist nicht unbedingt trivial. Und genau hier setzt meine Bachelorarbeit an. Ich habe versucht das Erstellen von Fluent Interfaces so weit wie möglich zu erleichtern. Mit Hilfe eines Modellers ist es nun möglich Fluent Interfaces in Diagrammen zu modellieren aus denen dann direkt der Code generiert werden kann.

Schau mal einfach unter: http://www.fluent-interfaces.com

Zwar wird zur Zeit Java-Code generiert aber das ganze ist so konzipiert, dass man schnell auch auf PHP-Code erweitern kann.

Grüße
Philipp

Blackflash schreibt:

Hallo Philipp.

Vielen Dank für deinen Hinweis. Daraus hat sich eine Perspektive ergeben, die mir bis dato nicht bewusst war. Habe deine Webseite sowie den Heise-Artikel, den ich auf der Seite gefunden habe, als Lesezeichen hinzugefügt. Werde es mir bei Gelegenheit ein wenig genauer anschauen.

gez. Andre

Kommentiere!

Your Name:


Your Email:


Your URL:


Spam Prevention:
Enter the text above into the box below.
If you are unable to read it, refresh the page.


Your Comment: