Angeregt durch ein Thema auf PHP hates me (und der dazugehörigen Diskussion) Stelle ich mich der Frage: Was?

Ehrlich gesagt fiel mir einfach kein besserer Einleitungssatz ein. Es handelt sich aber schon um ein Thema, dass mich länger beschäftigt, bisher gab es nur keine sinnvollen Gründe näher darüber nachzudenken, denn irgendwie sind die einzelnen Varianten sowieso austauschbar. Das Thema lautet: Suchen wir den Sinn hinter "Getter/Setter", "einzelner Accessor" oder wieso nicht gleich "public"?

Was ist Was?

Dazu ein bisschen Code. Man stelle sich dazu vor, der Code steht in irgendeiner x-beliebigen Klasse. Ich lasse einfach die Teile weg, die offensichtlich sind.
[php]// Variante 1: Getter/Setter
// Häufig umgesetzt mit Fluent Interface
protected $_x = null;
public function getX () {
return $this->_x;
}
public function setX ($x) {
$this->_x = $x;
return $this;
}

//------------------------------------------
// Variante 2: Einzelner Accessor
protected $_x = null;
public function x($x = null) {
$old = $this->_x;
if (!is_null ($x)) {
$this->_x = $x;
}
return $old;
}

//------------------------------------------
// Variante 3: Public Property
public $x = null;

//------------------------------------------
// Variante 4: Magisches public property
protected $_x = null;
public function __get ($var) {
$var = '__ . $var;
if (isset ($this->{$var})
return $this->{$var};
} else {
return null; // oder Fehler
}
}
public function __set ($var, $value) {
$var = '__ . $var;
if (isset ($this->{$var})
$this->{$var} = $value;
} else {
// Nichts, oder Fehler
}
}[/php]
Zunächst erstmal die Gemeinsamkeiten: Alle Varianten sorgen dafür, dass Attribute des Objektes verändert oder zurück gegeben werden. Alle (mit Ausnahme von 4) werden auch bei vielen Attributen nicht so umfangreich, dass sie unleserlich werden, sie vermehren sich bloss (Nr. 4) dagegen nicht). Mit allen Varianten ist es möglich (fast) vollständig auf Attribute zuzugreifen.

Die Eigenschaften

  1. Dies ist die einzige Variante, die das Fluent Interface ermöglicht. Wer allerdings (so wie ich) das Pattern sowieso ablehnt, hat davon nichts. Es ermöglicht den Zugriff auf das Attribut einzuschränken, zB in dem der Setter nur bestimmte Werte oder Typen akzeptiert, was auch oft Argumentationspol der Befürworter ist. Das sehe ich bloss eher als Augenwischerei, weil es selten umgesetzt wird. Ein Grund ist, dass die Typschwäche zB für Lazy-Loading genutzt wird, was eine Festlegung auf ein Typ erschwert. Und selbst wenn, wird oft vor Aufruf der Methode ebenfalls geprüft, um entsprechende Exception zu vermeiden: Double Validation. Will man zudem das Ziel (zB den Attributbezeichner) ändern, muss man gleich beide Methoden anpassen.
  2. Im Endeffekt ist dies nur eine aus Getter/Setter zusammengezogene, einzelne Methode. Kleineres Manko ist, dass das Fluent Interface nicht umgesetzt werden kann, größeres jedoch, dass null als Wert nicht möglich ist. Vorteil gegenüber 1): Es gibt nur eine Methode. Hier wurde bereits schon die Lesbarkeit kritisiert, bei 5 Zeilen seh ich das allerdings nicht ganz. Die sonstigen Vor- und Nachteile von 1) bleiben erhalten. Ebenso Kritik: Macht die Methode jetzt mehr, als es eine Methode sollte? Die Informatik-Philosophen könnten darüber sicher Glaubenskriege führen.
  3. Mal ernsthaft: Wenn meine Methode die Werte sowieso nur von/zum Attribut durchschleift, da kann man doch auch direkt auf das Attribut zugreifen, oder nicht? Offensichtliches Problem ist, dass nicht einfach das Ziel geändert werden kann, ebenso gibt es keine Möglichkeit zur Wert- oder Typprüfung, aber wie schon erwähnt, sieht man diese sowieso eher selten (was kein Grund sein soll, dass sie nicht wichtig werden kann). Bei Verwendung einer typschwachen Programmiersprache ist man sowieso zu einer besonderen Sorgfalt verpflichtet. Der Wert muss sinnvoll vorbelegt werden, weil dies nicht, wie bei 1) oder 2), erst beim ersten Abruf geschehen kann.
  4. Eigentlich eher besonders unschön, aber ich habe sie aus gutem Grund mit rein genommen: Sie vereinigt die Varianten 2 und 3 miteinander. Einerseits kann ich damit auf Attribute zugreifen, als wären sie Attribute, andererseits sind (theoretisch) Prüfungen etc möglich. Ebenso nicht zu verachten: Kommt man nachträglich auf die Idee mit Variante 3 Einschränkungen (was auch immer) einzuführen, ist diese Umstellung transparent. Es muss sich niemand weiteres drum kümmern. Kurz gefasst hebt es die Beschränkungen von 3) fast vollständig auf. Sooo hässlich ist sie vielleicht garnicht, die Nummer 4.

Zur Benutzung

Nochmal etwas Code vorweg. $a ist hier jeweils das entsprechende Objekt, $v irgendsoein Wert. Es gibt jeweils die Fälle "zuweisen", "abfragen", "mehrere Werte zuweisen" und "alten wert vor Zuweisung sichern" (in der Reihenfolge).
[php]// Variante 1
$a->setX ($v);
$v = $a->getX();
$a->setX ($v1)->setY ($v2); // Fluid
$v2 = $a->getX(); $a->setX ($v1); // Alter Wert separat sichern

//------------------------------------------
// Variante 2
$a->x ($v);
$v = $a->x();
$a->x ($v1); $a->y ($v2); // Kein fluid
$v2 = $a->x ($v1); // Alter Wert direkt auffangbar

//------------------------------------------
// Variante 3 und 4
$a->x = $v;
$v = $a->x;
$a->x = $v1; $a->y = $v2; // Kein fluid
$v2 = $a->x; $a->x = $a; // Alter Wert separat sichern[/php]
Wie schon vorher erwähnt fällt besonders auf, dass 3 und 4 nach Aussen identisch aussehen (können). Das setzt allerdings voraus, dass die magischen Methoden wie erwartet reagieren, andererseits leiden darunter alle Methoden-basierte Varianten: Wenn die Murks machen, gibts nichts zu retten.

Lässt man die Möglichkeit für Einschränkungen etc mal ausssen vor: Was ist denn nun übersichtlicher in der Verwendung? Meiner Empfindung nach unterscheiden sich die Varianten zu wenig, um eindeutig sagen zu können "Das ist es!". Einzig das Fluent Interface von 1) bzw die Rückgabe des alten Wertes von 2) bedürfen besonderer Dokumentation und sind demnach formal unübersichtlicher.

Nimmt man jetzt die Möglichkeiten, die Methoden bieten, mit rein, müssen diese auf jeden Fall ebenso dokumentiert werden, wenn sie denn genutzt werden. Da is 3) klar im Nachteil, wie gesagt: Vorausgesetzt man nutzt die Möglichkeit überhaupt.

Fazit

Es gibt keins. Selbst wenn ich hier jetzt die eine, wirklich gute Variante finden könnte, wiegen die Gepflogenheiten der meisten Entwickler doch schwerer. Wer jahrelang Getter/Setter gewohnt ist, wird einzig danach suchen. Ein (nicht-technischer) Vorteil der Variante ist zudem, dass sie mit Code-Completion diverser IDEs besser nutzbar ist. Unter Eclipse "get[Strg-Leertaste]" liefert bereits alle Getter. Nebenbei lassen sich alle 4 Varianten sowieso auch parallel nutzen. Ob das sinnvoll ist, bleibt offen.

Was mich angeht: Ich implementiere sowohl Getter/Setter, als auch das Fluent Interface (auch wenn ich es nicht nutze). Wieso? Man will doch niemanden verwirren ;)

Grüße,
Sebastian